/*-------------------------------------------------------------------------
 *
 * auth.c
 *	  处理网络身份验证的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/libpq/auth.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pwd.h>
#include <unistd.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include "commands/user.h"
#include "common/ip.h"
#include "common/md5.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/sasl.h"
#include "libpq/scram.h"
#include "miscadmin.h"
#include "port/pg_bswap.h"
#include "postmaster/postmaster.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"

/*----------------------------------------------------------------
 * 全局认证函数
 *----------------------------------------------------------------
 */
static void fc_auth_failed(Port *fc_port, int fc_status, const char *fc_logdetail);
static char *fc_recv_password_packet(Port *fc_port);
static void fc_set_authn_id(Port *fc_port, const char *fc_id);


/*----------------------------------------------------------------
 * 基于密码的认证方法（密码、md5 和 scram-sha-256）
 *----------------------------------------------------------------
 */
static int	fc_CheckPasswordAuth(Port *fc_port, const char **fc_logdetail);
static int	fc_CheckPWChallengeAuth(Port *fc_port, const char **fc_logdetail);

static int	fc_CheckMD5Auth(Port *fc_port, char *fc_shadow_pass,
						 const char **fc_logdetail);


/*----------------------------------------------------------------
 * Ident 认证
 *----------------------------------------------------------------
 */
/* 用户名 ident 服务器可以返回的最大大小（根据 RFC 1413） */
#define IDENT_USERNAME_MAX 512

/* Ident 服务的标准 TCP 端口号。由 IANA 分配 */
#define IDENT_PORT 113

static int	fc_ident_inet(hbaPort *fc_port);


/*----------------------------------------------------------------
 * 对等认证
 *----------------------------------------------------------------
 */
static int	fc_auth_peer(hbaPort *fc_port);


/*----------------------------------------------------------------
 * PAM 认证
 *----------------------------------------------------------------
 */
#ifdef USE_PAM
#ifdef HAVE_PAM_PAM_APPL_H
#include <pam/pam_appl.h>
#endif
#ifdef HAVE_SECURITY_PAM_APPL_H
#include <security/pam_appl.h>
#endif

#define PGSQL_PAM_SERVICE "postgresql"	/* 传递给 PAM 的服务名称 */

static int	fc_CheckPAMAuth(Port *fc_port, const char *fc_user, const char *fc_password);
static int	fc_pam_passwd_conv_proc(int fc_num_msg, const struct pam_message **fc_msg,
								 struct pam_response **fc_resp, void *fc_appdata_ptr);

static struct pam_conv pam_passw_conv = {
	&fc_pam_passwd_conv_proc,
	NULL
};

static const char *pam_passwd = NULL;	/* 针对 Solaris 2.6 的解决方法
										 * 的漏洞 */
static Port *pam_port_cludge;	/* 将 "Port *port" 传递到
								 * pam_passwd_conv_proc 的解决方法 */
static bool pam_no_password;	/* 用于检测未提供密码 */
#endif							/* USE_PAM */


/*----------------------------------------------------------------
 * BSD 认证
 *----------------------------------------------------------------
 */
#ifdef USE_BSD_AUTH
#include <bsd_auth.h>

static int	fc_CheckBSDAuth(Port *fc_port, char *fc_user);
#endif							/* USE_BSD_AUTH */


/*----------------------------------------------------------------
 * LDAP 认证
 *----------------------------------------------------------------
 */
#ifdef USE_LDAP
#ifndef WIN32
/* 我们使用一个已弃用的函数以保持代码路径与 win32 相同。 */
#define LDAP_DEPRECATED 1
#include <ldap.h>
#else
#include <winldap.h>

/* 来自平台 SDK 的正确标题 */
typedef
ULONG		(*__ldap_start_tls_sA) (IN PLDAP ExternalHandle,
									OUT PULONG ServerReturnValue,
									OUT LDAPMessage **result,
									IN PLDAPControlA * ServerControls,
									IN PLDAPControlA * ClientControls
);
#endif

static int	fc_CheckLDAPAuth(Port *fc_port);

/* LDAP_OPT_DIAGNOSTIC_MESSAGE 是较新的拼写 */
#ifndef LDAP_OPT_DIAGNOSTIC_MESSAGE
#define LDAP_OPT_DIAGNOSTIC_MESSAGE LDAP_OPT_ERROR_STRING
#endif

#endif							/* USE_LDAP */

/*----------------------------------------------------------------
 * 证书认证
 *----------------------------------------------------------------
 */
#ifdef USE_SSL
static int	fc_CheckCertAuth(Port *fc_port);
#endif


/*----------------------------------------------------------------
 * Kerberos 和 GSSAPI GUCs
 *----------------------------------------------------------------
 */
char	   *pg_krb_server_keyfile;
bool		pg_krb_caseins_users;


/*----------------------------------------------------------------
 * GSSAPI 认证
 *----------------------------------------------------------------
 */
#ifdef ENABLE_GSS
#include "libpq/be-gssapi-common.h"

static int	fc_pg_GSS_checkauth(Port *fc_port);
static int	fc_pg_GSS_recvauth(Port *fc_port);
#endif							/* ENABLE_GSS */


/*----------------------------------------------------------------
 * SSPI 认证
 *----------------------------------------------------------------
 */
#ifdef ENABLE_SSPI
typedef SECURITY_STATUS
			(WINAPI * QUERY_SECURITY_CONTEXT_TOKEN_FN) (PCtxtHandle, void **);
static int	pg_SSPI_recvauth(Port *port);
static int	pg_SSPI_make_upn(char *accountname,
							 size_t accountnamesize,
							 char *domainname,
							 size_t domainnamesize,
							 bool update_accountname);
#endif

/*----------------------------------------------------------------
 * RADIUS 认证
 *----------------------------------------------------------------
 */
static int	fc_CheckRADIUSAuth(Port *fc_port);
static int	fc_PerformRadiusTransaction(const char *fc_server, const char *fc_secret, const char *fc_portstr, const char *fc_identifier, const char *fc_user_name, const char *fc_passwd);


/*
 * 最大接受的 GSS 和 SSPI 身份验证令牌的大小。
 * 我们也将此用作普通密码数据包长度的限制。
 *
 * Kerberos 票证通常很小，但 Windows 域控制器发出的 TGT 包含一个称为特权属性证书（PAC）的授权字段，其中包含用户的 Windows 权限（组成员资格等）。PAC 会被复制到所有基于该 TGT 获得的票证中（即使是由 Windows 所信任的 Unix 领域发出的票证），其大小可以达到几 KB。Windows 系统接受的最大令牌大小由 MaxAuthToken Windows 注册表设置决定。微软建议不将其设置得高于 65535 字节，因此这似乎也是我们合理的限制。
 */
#define PG_MAX_AUTH_TOKEN_LENGTH	65535

/*----------------------------------------------------------------
 * 全局认证函数
 *----------------------------------------------------------------
 */

/*
 * 此钩子允许插件在客户端身份验证后控制，但在用户被告知结果之前。它可以用于记录登录事件，在身份验证失败后插入延迟等。
 */
ClientAuthentication_hook_type ClientAuthentication_hook = NULL;

/*
 * 告诉用户身份验证失败，但不（太多地）说明原因。
 *
 * 在安全性考虑和使合法用户生活不必要困难之间存在权衡。例如，我们不希望报告我们预计收到的密码...
 * 但报告用户名和使用的授权方法似乎很有用，而这些都是必须假定攻击者知道的事项。
 * 请注意，许多种类的失败会在邮局管理员日志中报告额外信息，我们希望这些信息仅限于好的人可读。特别是，如果 logdetail 不是 NULL，我们会将该字符串发送到日志中。
 */
static void fc_auth_failed(Port *fc_port, int fc_status, const char *fc_logdetail)
{
	const char *fc_errstr;
	char	   *fc_cdetail;
	int			fc_errcode_return = ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION;

	/*
	 * 如果我们由于客户端的 EOF 而失败，则直接退出；没有必要尝试向客户端发送消息，也没有太大意义在邮局管理员日志中记录该失败。（如果不是因为 libpq 在没有密码可发送时被挑战时会毫无预警地关闭连接，记录失败可能是可取的。对于每个在密码身份验证下的 psql 连接，我们将获得一条无用的日志条目，即便它是完全成功的，如果我们记录 STATUS_EOF 事件。）
	 */
	if (fc_status == STATUS_EOF)
		proc_exit(0);

	switch (fc_port->hba->auth_method)
	{
		case uaReject:
		case uaImplicitReject:
			fc_errstr = gettext_noop("authentication failed for user \"%s\": host rejected");
			break;
		case uaTrust:
			fc_errstr = gettext_noop("\"trust\" authentication failed for user \"%s\"");
			break;
		case uaIdent:
			fc_errstr = gettext_noop("Ident authentication failed for user \"%s\"");
			break;
		case uaPeer:
			fc_errstr = gettext_noop("Peer authentication failed for user \"%s\"");
			break;
		case uaPassword:
		case uaMD5:
#ifdef FDDGM
		case uaSM3:
#endif
		case uaSCRAM:
			fc_errstr = gettext_noop("password authentication failed for user \"%s\"");
			/* 我们用它来指示 .pgpass 密码失败。 */
			fc_errcode_return = ERRCODE_INVALID_PASSWORD;
			break;
		case uaGSS:
			fc_errstr = gettext_noop("GSSAPI authentication failed for user \"%s\"");
			break;
		case uaSSPI:
			fc_errstr = gettext_noop("SSPI authentication failed for user \"%s\"");
			break;
		case uaPAM:
			fc_errstr = gettext_noop("PAM authentication failed for user \"%s\"");
			break;
		case uaBSD:
			fc_errstr = gettext_noop("BSD authentication failed for user \"%s\"");
			break;
		case uaLDAP:
			fc_errstr = gettext_noop("LDAP authentication failed for user \"%s\"");
			break;
		case uaCert:
			fc_errstr = gettext_noop("certificate authentication failed for user \"%s\"");
			break;
		case uaRADIUS:
			fc_errstr = gettext_noop("RADIUS authentication failed for user \"%s\"");
			break;
		default:
			fc_errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
			break;
	}

	fc_cdetail = psprintf(_("Connection matched pg_hba.conf line %d: \"%s\""),
					   fc_port->hba->linenumber, fc_port->hba->rawline);
	if (fc_logdetail)
		fc_logdetail = psprintf("%s\n%s", fc_logdetail, fc_cdetail);
	else
		fc_logdetail = fc_cdetail;

	ereport(FATAL,
			(errcode(fc_errcode_return),
			 errmsg(fc_errstr, fc_port->user_name),
			 fc_logdetail ? errdetail_log("%s", fc_logdetail) : 0));

	/* 不返回 */
}


/*
 * 为当前用户设置已验证的身份。提供的字符串
 * 将被复制到 TopMemoryContext 中。如果
 * log_connections 被启用，则 ID 将被记录。
 *
 * 认证方法应在用户成功验证后立即调用此例程
 * 一次，即使他们知道授权可能会在后续失败。
 *
 * 提供的字符串将被复制到 TopMemoryContext，以匹配
 * Port 的生命周期，因此传递一个由外部库
 * 管理的字符串是安全的。
 */
static void fc_set_authn_id(Port *fc_port, const char *fc_id)
{
	Assert(fc_id);

	if (fc_port->authn_id)
	{
		/*
		 * 现有的 authn_id 永远不应被覆盖；这意味着两个
		 * 认证提供者在争斗（或者一个在自我争斗）。
		 * 不要向客户端泄漏任何认证详情，但也不要让
		 * 连接继续进行。
		 */
		ereport(FATAL,
				(errmsg("authentication identifier set more than once"),
				 errdetail_log("previous identifier: \"%s\"; new identifier: \"%s\"",
							   fc_port->authn_id, fc_id)));
	}

	fc_port->authn_id = MemoryContextStrdup(TopMemoryContext, fc_id);

	if (Log_connections)
	{
		ereport(LOG,
				errmsg("connection authenticated: identity=\"%s\" method=%s "
					   "(%s:%d)",
					   fc_port->authn_id, hba_authname(fc_port->hba->auth_method), HbaFileName,
					   fc_port->hba->linenumber));
	}
}


/*
 * 客户端认证从这里开始。如果发生错误，该
 * 函数不会返回，后端进程将被终止。
 */
void ClientAuthentication(Port *fc_port)
{
	int			fc_status = STATUS_ERROR;
	const char *fc_logdetail = NULL;

	/*
	 * 获取此前端/数据库
	 * 组合所使用的认证方法。注意：我们在此时不解析文件；
	 * 这已经在其他地方完成。如果解析 hba 配置文件失败，hba.c 会
	 * 在服务器日志文件中写入错误消息。
	 */
	hba_getauthmethod(fc_port);

	CHECK_FOR_INTERRUPTS();

	/*
	 * 这是我们首次获取当前连接的 hba 记录的
	 * 访问点，因此执行任何基于 hba
	 * 选项字段的验证，这些验证应在此处的认证之前完成。
	 */
	if (fc_port->hba->clientcert != clientCertOff)
	{
		/* 如果我们尚未加载根证书存储，则失败 */
		if (!secure_loaded_verify_locations())
			ereport(FATAL,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("client certificates can only be checked if a root certificate store is available")));

		/*
		 * 如果我们加载了根证书存储，并且客户机上存在证书，
		 * 则它已针对我们的根证书存储进行了验证，如果未通过验证，
		 * 连接将已被中止。
		 */
		if (!fc_port->peer_cert_valid)
			ereport(FATAL,
					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
					 errmsg("connection requires a valid client certificate")));
	}

	/*
	 * 现在继续进行实际的认证检查
	 */
	switch (fc_port->hba->auth_method)
	{
		case uaReject:

			/*
			 * pg_hba.conf 中的显式“拒绝”条目。此报告揭示
			 * 了存在显式拒绝条目的事实，这在安全
			 * 方面可能不是那么理想；但是对隐式拒绝的消息可能会
			 * 大大混淆 DBA，当真实情况是与显式拒绝匹配时。
			 * 我们也不想更改隐式拒绝的消息。正如
			 * 下面所指出的，这里显示的附加信息并未
			 * 暴露任何攻击者不知道的内容。
			 */
			{
				char		fc_hostinfo[NI_MAXHOST];
				const char *fc_encryption_state;

				pg_getnameinfo_all(&fc_port->raddr.addr, fc_port->raddr.salen,
								   fc_hostinfo, sizeof(fc_hostinfo),
								   NULL, 0,
								   NI_NUMERICHOST);

				fc_encryption_state =
#ifdef ENABLE_GSS
					(fc_port->gss && fc_port->gss->enc) ? _("GSS encryption") :
#endif
#ifdef USE_SSL
					fc_port->ssl_in_use ? _("SSL encryption") :
#endif
					_("no encryption");

				if (am_walsender && !am_db_walsender)
					ereport(FATAL,
							(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
					/* 翻译者：最后一个 %s 描述加密状态 */
							 errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\", %s",
									fc_hostinfo, fc_port->user_name,
									fc_encryption_state)));
				else
					ereport(FATAL,
							(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
					/* 翻译者：最后一个 %s 描述加密状态 */
							 errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\", %s",
									fc_hostinfo, fc_port->user_name,
									fc_port->database_name,
									fc_encryption_state)));
				break;
			}

		case uaImplicitReject:

			/*
			 * 没有匹配条目，因此告诉用户我们未能匹配。
			 *
			 * 注意：这里报告的额外信息不是安全漏洞，
			 * 因为所有这些信息在前端是已知的，必须
			 * 假定坏人也已知。我们只是帮助那些
			 * 不太了解情况的好人。
			 */
			{
				char		fc_hostinfo[NI_MAXHOST];
				const char *fc_encryption_state;

				pg_getnameinfo_all(&fc_port->raddr.addr, fc_port->raddr.salen,
								   fc_hostinfo, sizeof(fc_hostinfo),
								   NULL, 0,
								   NI_NUMERICHOST);

				fc_encryption_state =
#ifdef ENABLE_GSS
					(fc_port->gss && fc_port->gss->enc) ? _("GSS encryption") :
#endif
#ifdef USE_SSL
					fc_port->ssl_in_use ? _("SSL encryption") :
#endif
					_("no encryption");

#define HOSTNAME_LOOKUP_DETAIL(fc_port) \
				(fc_port->remote_hostname ? \
				 (fc_port->remote_hostname_resolv == +1 ? \
				  errdetail_log("Client IP address resolved to \"%s\", forward lookup matches.", \
								fc_port->remote_hostname) : \
				  fc_port->remote_hostname_resolv == 0 ? \
				  errdetail_log("Client IP address resolved to \"%s\", forward lookup not checked.", \
								fc_port->remote_hostname) : \
				  fc_port->remote_hostname_resolv == -1 ? \
				  errdetail_log("Client IP address resolved to \"%s\", forward lookup does not match.", \
								fc_port->remote_hostname) : \
				  fc_port->remote_hostname_resolv == -2 ? \
				  errdetail_log("Could not translate client host name \"%s\" to IP address: %s.", \
								fc_port->remote_hostname, \
								gai_strerror(fc_port->remote_hostname_errcode)) : \
				  0) \
				 : (fc_port->remote_hostname_resolv == -2 ? \
					errdetail_log("Could not resolve client IP address to a host name: %s.", \
								  gai_strerror(fc_port->remote_hostname_errcode)) : \
					0))

				if (am_walsender && !am_db_walsender)
					ereport(FATAL,
							(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
					/* 翻译者：最后一个 %s 描述加密状态 */
							 errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\", %s",
									fc_hostinfo, fc_port->user_name,
									fc_encryption_state),
							 HOSTNAME_LOOKUP_DETAIL(fc_port)));
				else
					ereport(FATAL,
							(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
					/* 翻译者：最后一个 %s 描述加密状态 */
							 errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s",
									fc_hostinfo, fc_port->user_name,
									fc_port->database_name,
									fc_encryption_state),
							 HOSTNAME_LOOKUP_DETAIL(fc_port)));
				break;
			}

		case uaGSS:
#ifdef ENABLE_GSS
			/* 我们可能已经有或没有 gss 工作区 */
			if (fc_port->gss == NULL)
				fc_port->gss = (pg_gssinfo *)
					MemoryContextAllocZero(TopMemoryContext,
										   sizeof(pg_gssinfo));
			fc_port->gss->auth = true;

			/*
			 * 如果在启用加密时设置了GSS状态，我们可以只
			 * 检查客户端的主体。否则，请询问它。
			 */
			if (fc_port->gss->enc)
				fc_status = fc_pg_GSS_checkauth(fc_port);
			else
			{
				sendAuthRequest(fc_port, AUTH_REQ_GSS, NULL, 0);
				fc_status = fc_pg_GSS_recvauth(fc_port);
			}
#else
			Assert(false);
#endif
			break;

		case uaSSPI:
#ifdef ENABLE_SSPI
			if (fc_port->gss == NULL)
				fc_port->gss = (pg_gssinfo *)
					MemoryContextAllocZero(TopMemoryContext,
										   sizeof(pg_gssinfo));
			sendAuthRequest(fc_port, AUTH_REQ_SSPI, NULL, 0);
			fc_status = pg_SSPI_recvauth(fc_port);
#else
			Assert(false);
#endif
			break;

		case uaPeer:
			fc_status = fc_auth_peer(fc_port);
			break;

		case uaIdent:
			fc_status = fc_ident_inet(fc_port);
			break;

		case uaMD5:
#ifdef FDDGM
		case uaSM3:
#endif
		case uaSCRAM:
			fc_status = fc_CheckPWChallengeAuth(fc_port, &fc_logdetail);
			break;

		case uaPassword:
			fc_status = fc_CheckPasswordAuth(fc_port, &fc_logdetail);
			break;

		case uaPAM:
#ifdef USE_PAM
			fc_status = fc_CheckPAMAuth(fc_port,fc_port->user_name, "");
#else
			Assert(false);
#endif							/* USE_PAM */
			break;

		case uaBSD:
#ifdef USE_BSD_AUTH
			fc_status = fc_CheckBSDAuth(fc_port, fc_port->user_name);
#else
			Assert(false);
#endif							/* USE_BSD_AUTH */
			break;

		case uaLDAP:
#ifdef USE_LDAP
			fc_status = fc_CheckLDAPAuth(fc_port);
#else
			Assert(false);
#endif
			break;
		case uaRADIUS:
			fc_status = fc_CheckRADIUSAuth(fc_port);
			break;
		case uaCert:
			/* uaCert将被视为clientcert=verify-full (uaTrust) */
		case uaTrust:
			fc_status = STATUS_OK;
			break;
	}

	if ((fc_status == STATUS_OK && fc_port->hba->clientcert == clientCertFull)
		|| fc_port->hba->auth_method == uaCert)
	{
		/*
		 * 确保只有在使用证书方法
		 * 或verify-full选项时才检查证书。
		 */
#ifdef USE_SSL
		fc_status = fc_CheckCertAuth(fc_port);
#else
		Assert(false);
#endif
	}

	if (ClientAuthentication_hook)
		(*ClientAuthentication_hook) (fc_port, fc_status);

	if (fc_status == STATUS_OK)
		sendAuthRequest(fc_port, AUTH_REQ_OK, NULL, 0);
	else
		fc_auth_failed(fc_port, fc_status, fc_logdetail);
}


/*
 * 向前端发送身份验证请求数据包。
 */
void sendAuthRequest(Port *fc_port, AuthRequest fc_areq, const char *fc_extradata, int fc_extralen)
{
	StringInfoData fc_buf;

	CHECK_FOR_INTERRUPTS();

	pq_beginmessage(&fc_buf, 'R');
	pq_sendint32(&fc_buf, (int32) fc_areq);
	if (fc_extralen > 0)
		pq_sendbytes(&fc_buf, fc_extradata, fc_extralen);

	pq_endmessage(&fc_buf);

	/*
	 * 刷新消息以便客户端可以看到，除了AUTH_REQ_OK和
	 * AUTH_REQ_SASL_FIN，直到我们准备好
	 * 查询之前不需要发送它们。
	 */
	if (fc_areq != AUTH_REQ_OK && fc_areq != AUTH_REQ_SASL_FIN)
		pq_flush();

	CHECK_FOR_INTERRUPTS();
}

/*
 * 从前端收集密码响应数据包。
 *
 * 如果无法获取密码，则返回NULL，否则返回palloc的字符串。
 */
static char * fc_recv_password_packet(Port *fc_port)
{
	StringInfoData fc_buf;
	int			fc_mtype;

	pq_startmsgread();

	/* 期待'p'消息类型 */
	fc_mtype = pq_getbyte();
	if (fc_mtype != 'p')
	{
		/*
		 * 如果客户端仅仅在不提供密码的情况下断开连接，不要
		 * 创建日志条目。这在协议规范中是合法的，实际上
		 * psql通常也这样做，因此抱怨只会使日志杂乱。
		 */
		if (fc_mtype != EOF)
			ereport(ERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("expected password response, got message type %d",
							fc_mtype)));
		return NULL;			/* EOF或坏消息类型 */
	}

	initStringInfo(&fc_buf);
	if (pq_getmessage(&fc_buf, PG_MAX_AUTH_TOKEN_LENGTH))	/* 接收密码 */
	{
		/* EOF - pq_getmessage已经记录了合适的消息 */
		pfree(fc_buf.data);
		return NULL;
	}

	/*
	 * 进行合理性检查：密码数据包的长度应与
	 * 包含字符串的长度一致。注意在这里使用strlen是安全的，因为
	 * StringInfo保证有一个附加的'\0'。
	 */
	if (strlen(fc_buf.data) + 1 != fc_buf.len)
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("invalid password packet size")));

	/*
	 * 不允许空密码。Libpq将空密码视为
	 * 完全没有密码，甚至不会尝试进行身份验证。但其他
	 * 客户端可能会尝试，因此允许它会造成混淆。
	 *
	 * 注意，这仅能捕获客户端以明文形式发送的空密码。
	 * 在CREATE/ALTER USER中还有一个检查，防止将
	 * 空字符串存储为用户密码。我们依赖于该检查进行MD5和SCRAM身份验证，
	 * 但我们仍然需要在这里进行此检查，以防止空密码
	 * 用于验证方法中，该方法会将密码与外部
	 * 系统（如PAM、LDAP和RADIUS）进行检查。
	 */
	if (fc_buf.len == 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PASSWORD),
				 errmsg("empty password returned by client")));

	/* 出于安全原因，不要将密码回显到日志中。 */
	elog(DEBUG5, "received password packet");

	/*
	 * 返回接收到的字符串。注意我们不尝试对其进行任何
	 * 字符集转换；因为我们还不知道客户端的
	 * 编码，所以没有太大意义。
	 */
	return fc_buf.data;
}


/*----------------------------------------------------------------
 * 基于密码的身份验证机制
 *----------------------------------------------------------------
 */

/*
 * 明文密码身份验证。
 */
static int fc_CheckPasswordAuth(Port *fc_port, const char **fc_logdetail)
{
	char	   *fc_passwd;
	int			fc_result;
	char	   *fc_shadow_pass;

	sendAuthRequest(fc_port, AUTH_REQ_PASSWORD, NULL, 0);

	fc_passwd = fc_recv_password_packet(fc_port);
	if (fc_passwd == NULL)
		return STATUS_EOF;		/* 客户端不会发送密码 */

	fc_shadow_pass = get_role_password(fc_port->user_name, fc_logdetail);
	if (fc_shadow_pass)
	{
		fc_result = plain_crypt_verify(fc_port->user_name, fc_shadow_pass, fc_passwd,
									fc_logdetail);
	}
	else
		fc_result = STATUS_ERROR;

	if (fc_shadow_pass)
		pfree(fc_shadow_pass);
	pfree(fc_passwd);

	if (fc_result == STATUS_OK)
		fc_set_authn_id(fc_port, fc_port->user_name);

	return fc_result;
}

#ifdef FDDGM
static int fc_CheckSM3Auth(Port *fc_port, char *fc_shadow_pass, const char **fc_logdetail)
{
	char		fc_salt[4];		/* 密码盐 */
	char	   *fc_passwd;
	int			fc_result;

	if (Db_user_namespace)
		ereport(FATAL,
				(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
				 errmsg("SM3 authentication is not supported when \"db_user_namespace\" is enabled")));

	/* 包含用于计算响应的盐 */
	if (!pg_strong_random(fc_salt, 4))
	{
		ereport(LOG,
				(errmsg("could not generate random sm3 salt")));
		return STATUS_ERROR;
	}

	sendAuthRequest(fc_port, AUTH_REQ_SM3, fc_salt, 4);

	fc_passwd = fc_recv_password_packet(fc_port);
	if (fc_passwd == NULL)
		return STATUS_EOF;		/* 客户端不会发送密码 */

	if (fc_shadow_pass)
		fc_result = sm3_crypt_verify(fc_port->user_name, fc_shadow_pass, fc_passwd,
								  fc_salt, 4, fc_logdetail);
	else
		fc_result = STATUS_ERROR;

	pfree(fc_passwd);

	return fc_result;
}
#endif

/*
 * MD5和SCRAM身份验证。
 */
static int fc_CheckPWChallengeAuth(Port *fc_port, const char **fc_logdetail)
{
	int			fc_auth_result;
	char	   *fc_shadow_pass;
	PasswordType fc_pwtype;

	Assert(fc_port->hba->auth_method == uaSCRAM ||
#ifdef FDDGM
			fc_port->hba->auth_method == uaSM3 ||
#endif
		   fc_port->hba->auth_method == uaMD5);

	/* 首先查找用户的密码。 */
	fc_shadow_pass = get_role_password(fc_port->user_name, fc_logdetail);

	/*
	 * 如果用户不存在，或者没有密码或密码已过期，我们
	 * 仍然进行身份验证，以避免向客户端透露用户不存在。 如果允许使用'md5'，我们将根据当前的
	 * password_encryption 设置选择使用'md5'还是'scram-sha-256'身份验证。 这样做的目的是大多数真实用户
	 * 可能拥有这种类型的密码，如果我们假装该用户
	 * 也有这种类型的密码，它就能够“融入”得最好。
	 */
	if (!fc_shadow_pass)
		fc_pwtype = Password_encryption;
	else
		fc_pwtype = get_password_type(fc_shadow_pass);

	/*
	 * 如果允许使用'md5'身份验证，则根据用户
	 * 拥有的密码类型决定执行'md5'或
	 * 'scram-sha-256'身份验证。 如果是MD5哈希，我们必须进行MD5身份验证，如果是
	 * SCRAM密钥，我们必须进行SCRAM身份验证。
	 *
	 * 如果不允许MD5身份验证，则始终使用SCRAM。 如果用户
	 * 有MD5密码，则使用SCRAM机制的CheckSASLAuth()将
	 * 失败。
	 */
	if (fc_port->hba->auth_method == uaMD5 && fc_pwtype == PASSWORD_TYPE_MD5)
		fc_auth_result = fc_CheckMD5Auth(fc_port, fc_shadow_pass, fc_logdetail);
#ifdef FDDGM
	else if(fc_port->hba->auth_method == uaSM3 && fc_pwtype == PASSWORD_TYPE_SM3)
		fc_auth_result = fc_CheckSM3Auth(fc_port, fc_shadow_pass, fc_logdetail);
#endif
	else
		fc_auth_result = CheckSASLAuth(&pg_be_scram_mech, fc_port, fc_shadow_pass,
									fc_logdetail);

	if (fc_shadow_pass)
		pfree(fc_shadow_pass);
	else
	{
		/*
		 * 如果get_role_password()返回错误，则身份验证最好不
		 * 成功。
		 */
//#ifdef FDD
		//返回STATUS_EOF代表客户端没有传密码过来，需要保持这个错误码，以备ClientAuthentication_hook中进行特殊处理
//#endif
		Assert(fc_auth_result != STATUS_OK);
	}

	if (fc_auth_result == STATUS_OK)
		fc_set_authn_id(fc_port, fc_port->user_name);

	return fc_auth_result;
}

static int fc_CheckMD5Auth(Port *fc_port, char *fc_shadow_pass, const char **fc_logdetail)
{
	char		fc_md5Salt[4];		/* 密码盐 */
	char	   *fc_passwd;
	int			fc_result;

	if (Db_user_namespace)
		ereport(FATAL,
				(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
				 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));

	/* 包含用于计算响应的盐 */
	if (!pg_strong_random(fc_md5Salt, 4))
	{
		ereport(LOG,
				(errmsg("could not generate random MD5 salt")));
		return STATUS_ERROR;
	}

	sendAuthRequest(fc_port, AUTH_REQ_MD5, fc_md5Salt, 4);

	fc_passwd = fc_recv_password_packet(fc_port);
	if (fc_passwd == NULL)
		return STATUS_EOF;		/* 客户端不会发送密码 */

	if (fc_shadow_pass)
		fc_result = md5_crypt_verify(fc_port->user_name, fc_shadow_pass, fc_passwd,
								  fc_md5Salt, 4, fc_logdetail);
	else
		fc_result = STATUS_ERROR;

	pfree(fc_passwd);

	return fc_result;
}


/*----------------------------------------------------------------
 * GSSAPI身份验证系统
 *----------------------------------------------------------------
 */
#ifdef ENABLE_GSS
static int fc_pg_GSS_recvauth(Port *fc_port)
{
	OM_uint32	fc_maj_stat,
				fc_min_stat,
				fc_lmin_s,
				fc_gflags;
	int			fc_mtype;
	StringInfoData fc_buf;
	gss_buffer_desc fc_gbuf;

	/*
	 * 如果有已配置的keytab，则使用它。不幸的是，Heimdal
	 * 不支持凭据存储扩展，因此使用环境变量。
	 */
	if (pg_krb_server_keyfile != NULL && pg_krb_server_keyfile[0] != '\0')
	{
		if (setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1) != 0)
		{
			/* 唯一可能的失败原因是OOM，因此使用该错误代码 */
			ereport(FATAL,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("could not set environment: %m")));
		}
	}

	/*
	 * 我们接受我们keytab中存在的任何服务主体。这
	 * 增加了不同Kerberos实现之间的互操作性，例如在
	 * 区分大小写方面的差异，但并没有真正打开
	 * 任何攻击向量。
	 */
	fc_port->gss->cred = GSS_C_NO_CREDENTIAL;

	/*
	 * 用空上下文初始化序列
	 */
	fc_port->gss->ctx = GSS_C_NO_CONTEXT;

	/*
	 * 循环通过GSSAPI消息交换。 此交换可以由
	 * 双向发送的多个消息组成。 第一个消息始终来自
	 * 客户端。 从客户端到服务器的所有消息都是密码数据包
	 * (类型为'p')。
	 */
	do
	{
		pq_startmsgread();

		CHECK_FOR_INTERRUPTS();

		fc_mtype = pq_getbyte();
		if (fc_mtype != 'p')
		{
			/* 仅在客户端未断开连接时记录错误。 */
			if (fc_mtype != EOF)
				ereport(ERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("expected GSS response, got message type %d",
								fc_mtype)));
			return STATUS_ERROR;
		}

		/* 获取实际的GSS令牌 */
		initStringInfo(&fc_buf);
		if (pq_getmessage(&fc_buf, PG_MAX_AUTH_TOKEN_LENGTH))
		{
			/* EOF - pq_getmessage已记录错误 */
			pfree(fc_buf.data);
			return STATUS_ERROR;
		}

		/* 映射到GSSAPI样式缓冲区 */
		fc_gbuf.length = fc_buf.len;
		fc_gbuf.value = fc_buf.data;

		elog(DEBUG4, "processing received GSS token of length %u",
			 (unsigned int) fc_gbuf.length);

		fc_maj_stat = gss_accept_sec_context(&fc_min_stat,
										  &fc_port->gss->ctx,
										  fc_port->gss->cred,
										  &fc_gbuf,
										  GSS_C_NO_CHANNEL_BINDINGS,
										  &fc_port->gss->name,
										  NULL,
										  &fc_port->gss->outbuf,
										  &fc_gflags,
										  NULL,
										  NULL);

		/* gbuf不再使用 */
		pfree(fc_buf.data);

		elog(DEBUG5, "gss_accept_sec_context major: %u, "
			 "minor: %u, outlen: %u, outflags: %x",
			 fc_maj_stat, fc_min_stat,
			 (unsigned int) fc_port->gss->outbuf.length, fc_gflags);

		CHECK_FOR_INTERRUPTS();

		if (fc_port->gss->outbuf.length != 0)
		{
			/*
			 * 协商生成的数据将发送给客户端。
			 */
			elog(DEBUG4, "sending GSS response token of length %u",
				 (unsigned int) fc_port->gss->outbuf.length);

			sendAuthRequest(fc_port, AUTH_REQ_GSS_CONT,
							fc_port->gss->outbuf.value, fc_port->gss->outbuf.length);

			gss_release_buffer(&fc_lmin_s, &fc_port->gss->outbuf);
		}

		if (fc_maj_stat != GSS_S_COMPLETE && fc_maj_stat != GSS_S_CONTINUE_NEEDED)
		{
			gss_delete_sec_context(&fc_lmin_s, &fc_port->gss->ctx, GSS_C_NO_BUFFER);
			pg_GSS_error(_("accepting GSS security context failed"),
						 fc_maj_stat, fc_min_stat);
			return STATUS_ERROR;
		}

		if (fc_maj_stat == GSS_S_CONTINUE_NEEDED)
			elog(DEBUG4, "GSS continue needed");

	} while (fc_maj_stat == GSS_S_CONTINUE_NEEDED);

	if (fc_port->gss->cred != GSS_C_NO_CREDENTIAL)
	{
		/*
		 * 释放服务主体凭证
		 */
		gss_release_cred(&fc_min_stat, &fc_port->gss->cred);
	}
	return fc_pg_GSS_checkauth(fc_port);
}

/*
 * 检查GSSAPI身份验证用户是否被允许以声明的用户名进行连接。
 */
static int fc_pg_GSS_checkauth(Port *fc_port)
{
	int			fc_ret;
	OM_uint32	fc_maj_stat,
				fc_min_stat,
				fc_lmin_s;
	gss_buffer_desc fc_gbuf;
	char	   *fc_princ;

	/*
	 * 获取经过身份验证的用户的名称，并将其与连接时指定的pg
	 * 用户名进行比较。
	 */
	fc_maj_stat = gss_display_name(&fc_min_stat, fc_port->gss->name, &fc_gbuf, NULL);
	if (fc_maj_stat != GSS_S_COMPLETE)
	{
		pg_GSS_error(_("retrieving GSS user name failed"),
					 fc_maj_stat, fc_min_stat);
		return STATUS_ERROR;
	}

	/*
	 * gbuf.value可能没有以null结尾，因此将其转换为常规
	 * 以null结尾的字符串。
	 */
	fc_princ = palloc(fc_gbuf.length + 1);
	memcpy(fc_princ, fc_gbuf.value, fc_gbuf.length);
	fc_princ[fc_gbuf.length] = '\0';
	gss_release_buffer(&fc_lmin_s, &fc_gbuf);

	/*
	 * 将经过身份验证的主体的原始名称复制到我们的后端
	 * 内存中以便稍后显示。
	 *
	 * 这也是我们的身份验证身份。 现在设置它，而不是
	 * 等待下面的usermap检查，因为身份验证已经
	 * 成功，我们希望日志文件反映这一点。
	 */
	fc_port->gss->princ = MemoryContextStrdup(TopMemoryContext, fc_princ);
	fc_set_authn_id(fc_port, fc_princ);

	/*
	 * 在领域分隔符处拆分用户名
	 */
	if (strchr(fc_princ, '@'))
	{
		char	   *fc_cp = strchr(fc_princ, '@');

		/*
		 * 如果我们不打算在传递给 ident 映射的用户名中包含领域，
		 * 则在此处以破坏性方式修改它以删除领域。然后，移动到分隔符后
		 * 检查领域。
		 */
		if (!fc_port->hba->include_realm)
			*fc_cp = '\0';
		fc_cp++;

		if (fc_port->hba->krb_realm != NULL && strlen(fc_port->hba->krb_realm))
		{
			/*
			 * 首先匹配名称的领域部分
			 */
			if (pg_krb_caseins_users)
				fc_ret = pg_strcasecmp(fc_port->hba->krb_realm, fc_cp);
			else
				fc_ret = strcmp(fc_port->hba->krb_realm, fc_cp);

			if (fc_ret)
			{
				/* GSS 领域不匹配 */
				elog(DEBUG2,
					 "GSSAPI realm (%s) and configured realm (%s) don't match",
					 fc_cp, fc_port->hba->krb_realm);
				pfree(fc_princ);
				return STATUS_ERROR;
			}
		}
	}
	else if (fc_port->hba->krb_realm && strlen(fc_port->hba->krb_realm))
	{
		elog(DEBUG2,
			 "GSSAPI did not return realm but realm matching was requested");
		pfree(fc_princ);
		return STATUS_ERROR;
	}

	fc_ret = check_usermap(fc_port->hba->usermap, fc_port->user_name, fc_princ,
						pg_krb_caseins_users);

	pfree(fc_princ);

	return fc_ret;
}
#endif							/* ENABLE_GSS */


/*----------------------------------------------------------------
 * SSPI 认证系统
 *----------------------------------------------------------------
 */
#ifdef ENABLE_SSPI

/*
 * 为 SSPI 认证生成错误。调用者应该对 errmsg 应用
 * _() 以使其可翻译。
 */
static void pg_SSPI_error(int severity, const char *errmsg, SECURITY_STATUS r)
{
	char		sysmsg[256];

	if (FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS |
					  FORMAT_MESSAGE_FROM_SYSTEM,
					  NULL, r, 0,
					  sysmsg, sizeof(sysmsg), NULL) == 0)
		ereport(severity,
				(errmsg_internal("%s", errmsg),
				 errdetail_internal("SSPI error %x", (unsigned int) r)));
	else
		ereport(severity,
				(errmsg_internal("%s", errmsg),
				 errdetail_internal("%s (%x)", sysmsg, (unsigned int) r)));
}

static int pg_SSPI_recvauth(Port *port)
{
	int			mtype;
	StringInfoData buf;
	SECURITY_STATUS r;
	CredHandle	sspicred;
	CtxtHandle *sspictx = NULL,
				newctx;
	TimeStamp	expiry;
	ULONG		contextattr;
	SecBufferDesc inbuf;
	SecBufferDesc outbuf;
	SecBuffer	OutBuffers[1];
	SecBuffer	InBuffers[1];
	HANDLE		token;
	TOKEN_USER *tokenuser;
	DWORD		retlen;
	char		accountname[MAXPGPATH];
	char		domainname[MAXPGPATH];
	DWORD		accountnamesize = sizeof(accountname);
	DWORD		domainnamesize = sizeof(domainname);
	SID_NAME_USE accountnameuse;
	HMODULE		secur32;
	char	   *authn_id;

	QUERY_SECURITY_CONTEXT_TOKEN_FN _QuerySecurityContextToken;

	/*
	 * 获取服务器凭据的句柄。
	 */
	r = AcquireCredentialsHandle(NULL,
								 "negotiate",
								 SECPKG_CRED_INBOUND,
								 NULL,
								 NULL,
								 NULL,
								 NULL,
								 &sspicred,
								 &expiry);
	if (r != SEC_E_OK)
		pg_SSPI_error(ERROR, _("could not acquire SSPI credentials"), r);

	/*
	 * 在 SSPI 消息交换中循环。此交换可以由多条消息组成，
	 * 向两个方向发送。第一条消息总是来自客户端。所有从客户端到服务器的消息都是密码数据包
	 * （类型 'p'）。
	 */
	do
	{
		pq_startmsgread();
		mtype = pq_getbyte();
		if (mtype != 'p')
		{
			if (sspictx != NULL)
			{
				DeleteSecurityContext(sspictx);
				free(sspictx);
			}
			FreeCredentialsHandle(&sspicred);

			/* 仅在客户端未断开连接时记录错误。 */
			if (mtype != EOF)
				ereport(ERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("expected SSPI response, got message type %d",
								mtype)));
			return STATUS_ERROR;
		}

		/* 获取实际的 SSPI 令牌 */
		initStringInfo(&buf);
		if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH))
		{
			/* EOF - pq_getmessage已记录错误 */
			pfree(buf.data);
			if (sspictx != NULL)
			{
				DeleteSecurityContext(sspictx);
				free(sspictx);
			}
			FreeCredentialsHandle(&sspicred);
			return STATUS_ERROR;
		}

		/* 映射到 SSPI 样式缓冲区 */
		inbuf.ulVersion = SECBUFFER_VERSION;
		inbuf.cBuffers = 1;
		inbuf.pBuffers = InBuffers;
		InBuffers[0].pvBuffer = buf.data;
		InBuffers[0].cbBuffer = buf.len;
		InBuffers[0].BufferType = SECBUFFER_TOKEN;

		/* 准备输出缓冲区 */
		OutBuffers[0].pvBuffer = NULL;
		OutBuffers[0].BufferType = SECBUFFER_TOKEN;
		OutBuffers[0].cbBuffer = 0;
		outbuf.cBuffers = 1;
		outbuf.pBuffers = OutBuffers;
		outbuf.ulVersion = SECBUFFER_VERSION;

		elog(DEBUG4, "processing received SSPI token of length %u",
			 (unsigned int) buf.len);

		r = AcceptSecurityContext(&sspicred,
								  sspictx,
								  &inbuf,
								  ASC_REQ_ALLOCATE_MEMORY,
								  SECURITY_NETWORK_DREP,
								  &newctx,
								  &outbuf,
								  &contextattr,
								  NULL);

		/* 输入缓冲区不再使用 */
		pfree(buf.data);

		if (outbuf.cBuffers > 0 && outbuf.pBuffers[0].cbBuffer > 0)
		{
			/*
			 * 协商生成的数据将发送给客户端。
			 */
			elog(DEBUG4, "sending SSPI response token of length %u",
				 (unsigned int) outbuf.pBuffers[0].cbBuffer);

			port->gss->outbuf.length = outbuf.pBuffers[0].cbBuffer;
			port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;

			sendAuthRequest(port, AUTH_REQ_GSS_CONT,
							port->gss->outbuf.value, port->gss->outbuf.length);

			FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
		}

		if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED)
		{
			if (sspictx != NULL)
			{
				DeleteSecurityContext(sspictx);
				free(sspictx);
			}
			FreeCredentialsHandle(&sspicred);
			pg_SSPI_error(ERROR,
						  _("could not accept SSPI security context"), r);
		}

		/*
		 * 用我们刚收到的上下文覆盖当前上下文。如果
		 * sspictx 为 NULL，说明是第一次循环，我们需要为它分配一个
		 * 缓冲区。在后续运行中，我们可以直接覆盖缓冲区
		 * 内容，因为大小不会改变。
		 */
		if (sspictx == NULL)
		{
			sspictx = malloc(sizeof(CtxtHandle));
			if (sspictx == NULL)
#ifdef FDD //cppcheck
			{
#endif
				ereport(ERROR,
						(errmsg("out of memory")));
#ifdef FDD //cppcheck
				return STATUS_ERROR;
			}
#endif
		}

		memcpy(sspictx, &newctx, sizeof(CtxtHandle));

		if (r == SEC_I_CONTINUE_NEEDED)
			elog(DEBUG4, "SSPI continue needed");

	} while (r == SEC_I_CONTINUE_NEEDED);


	/*
	 * 释放服务主体凭据
	 */
	FreeCredentialsHandle(&sspicred);


	/*
	 * SEC_E_OK 表示认证现在已完成。
	 *
	 * 获取已认证用户的名称，并将其与连接中指定的 pg
	 * 用户名进行比较。
	 *
	 * MingW 缺少 secur32 库中 QuerySecurityContextToken 的导出，
	 * 所以我们必须动态加载它。
	 */

	secur32 = LoadLibrary("SECUR32.DLL");
	if (secur32 == NULL)
		ereport(ERROR,
				(errmsg("could not load library \"%s\": error code %lu",
						"SECUR32.DLL", GetLastError())));

	_QuerySecurityContextToken = (QUERY_SECURITY_CONTEXT_TOKEN_FN) (pg_funcptr_t)
		GetProcAddress(secur32, "QuerySecurityContextToken");
	if (_QuerySecurityContextToken == NULL)
	{
		FreeLibrary(secur32);
		ereport(ERROR,
				(errmsg_internal("could not locate QuerySecurityContextToken in secur32.dll: error code %lu",
								 GetLastError())));
	}

	r = (_QuerySecurityContextToken) (sspictx, &token);
	if (r != SEC_E_OK)
	{
		FreeLibrary(secur32);
		pg_SSPI_error(ERROR,
					  _("could not get token from SSPI security context"), r);
	}

	FreeLibrary(secur32);

	/*
	 * 不再需要安全上下文，从这里开始一切都使用令牌。
	 */
	DeleteSecurityContext(sspictx);
	free(sspictx);

	if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122)
		ereport(ERROR,
				(errmsg_internal("could not get token information buffer size: error code %lu",
								 GetLastError())));

	tokenuser = malloc(retlen);
	if (tokenuser == NULL)
		ereport(ERROR,
				(errmsg("out of memory")));

	if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen))
		ereport(ERROR,
				(errmsg_internal("could not get token information: error code %lu",
								 GetLastError())));

	CloseHandle(token);

	if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize,
						  domainname, &domainnamesize, &accountnameuse))
		ereport(ERROR,
				(errmsg_internal("could not look up account SID: error code %lu",
								 GetLastError())));

	free(tokenuser);

	if (!port->hba->compat_realm)
	{
		int			status = pg_SSPI_make_upn(accountname, sizeof(accountname),
											  domainname, sizeof(domainname),
											  port->hba->upn_username);

		if (status != STATUS_OK)
			/* 从 pg_SSPI_make_upn 已报告错误 */
			return status;
	}

	/*
	 * 我们拥有构造已认证身份所需的所有信息。现在设置它，而不是等待下面的 check_usermap，
	 * 因为认证已经成功，我们希望日志文件反映这一点。
	 */
	if (port->hba->compat_realm)
	{
		/* SAM 兼容格式。 */
		authn_id = psprintf("%s\\%s", domainname, accountname);
	}
	else
	{
		/* Kerberos 主体格式。 */
		authn_id = psprintf("%s@%s", accountname, domainname);
	}

	set_authn_id(port, authn_id);
	pfree(authn_id);

	/*
	 * 如果请求，比较领域/域。在 SSPI 中，总是进行不区分大小写的比较。
	 */
	if (port->hba->krb_realm && strlen(port->hba->krb_realm))
	{
		if (pg_strcasecmp(port->hba->krb_realm, domainname) != 0)
		{
			elog(DEBUG2,
				 "SSPI domain (%s) and configured domain (%s) don't match",
				 domainname, port->hba->krb_realm);

			return STATUS_ERROR;
		}
	}

	/*
	 * 我们在 accountname 中有用户名（不带域/领域），与
	 * 提供的值进行比较。在 SSPI 中，总是进行不区分大小写的比较。
	 *
	 * 如果设置为包含领域，则以 <用户名>@<领域> 格式附加。
	 */
	if (port->hba->include_realm)
	{
		char	   *namebuf;
		int			retval;

		namebuf = psprintf("%s@%s", accountname, domainname);
		retval = check_usermap(port->hba->usermap, port->user_name, namebuf, true);
		pfree(namebuf);
		return retval;
	}
	else
		return check_usermap(port->hba->usermap, port->user_name, accountname, true);
}

/*
 * 用 Kerberos 领域名称替换域名，
 * 并可选地用 Kerberos 用户名替换账户名。
 */
static int pg_SSPI_make_upn(char *accountname,
				 size_t accountnamesize,
				 char *domainname,
				 size_t domainnamesize,
				 bool update_accountname)
{
	char	   *samname;
	char	   *upname = NULL;
	char	   *p = NULL;
	ULONG		upnamesize = 0;
	size_t		upnamerealmsize;
	BOOLEAN		res;

	/*
	 * 构建SAM名称（DOMAIN\user），然后转换为UPN
	 * （user@kerberos.realm）。领域名称以小写返回，但这没关系，
	 * 因为在SSPI认证中，字符串比较总是大小写不敏感。
	 */

	samname = psprintf("%s\\%s", domainname, accountname);
	res = TranslateName(samname, NameSamCompatible, NameUserPrincipal,
						NULL, &upnamesize);

	if ((!res && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
		|| upnamesize == 0)
	{
		pfree(samname);
		ereport(LOG,
				(errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
				 errmsg("could not translate name")));
		return STATUS_ERROR;
	}

	/* upnamesize包含终止的NUL。 */
	upname = palloc(upnamesize);

	res = TranslateName(samname, NameSamCompatible, NameUserPrincipal,
						upname, &upnamesize);

	pfree(samname);
	if (res)
		p = strchr(upname, '@');

	if (!res || p == NULL)
	{
		pfree(upname);
		ereport(LOG,
				(errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
				 errmsg("could not translate name")));
		return STATUS_ERROR;
	}

	/* '@'之后的领域名称长度，包括NUL。 */
	upnamerealmsize = upnamesize - (p - upname + 1);

	/* 用领域名称替换域名。 */
	if (upnamerealmsize > domainnamesize)
	{
		pfree(upname);
		ereport(LOG,
				(errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
				 errmsg("realm name too long")));
		return STATUS_ERROR;
	}

	/* 现在长度是安全的。 */
	strcpy(domainname, p + 1);

	/* 也替换账户名称（以防UPN != SAM）? */
	if (update_accountname)
	{
		if ((p - upname + 1) > accountnamesize)
		{
			pfree(upname);
			ereport(LOG,
					(errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
					 errmsg("translated account name too long")));
			return STATUS_ERROR;
		}

		*p = 0;
		strcpy(accountname, upname);
	}

	pfree(upname);
	return STATUS_OK;
}
#endif							/* ENABLE_SSPI */



/*----------------------------------------------------------------
 * Ident认证系统
 *----------------------------------------------------------------
 */

/*
 *	将字符串"*ident_response"解析为来自Ident
 *	服务器查询的响应。如果这是一个正常的响应，表示用户名，则返回true
 *	并将用户名存储在*ident_user中。如果是其他内容，
 *	则返回false。
 */
static bool fc_interpret_ident_response(const char *fc_ident_response,
						 char *fc_ident_user)
{
	const char *fc_cursor = fc_ident_response;	/* 光标指向*ident_response */

	/*
	 * Ident的响应，在telnet传统中，应该以crlf（\r\n）结尾。
	 */
	if (strlen(fc_ident_response) < 2)
		return false;
	else if (fc_ident_response[strlen(fc_ident_response) - 2] != '\r')
		return false;
	else
	{
		while (*fc_cursor != ':' && *fc_cursor != '\r')
			fc_cursor++;			/* 跳过端口字段 */

		if (*fc_cursor != ':')
			return false;
		else
		{
			/* 我们的位置在响应类型字段前的冒号之前 */
			char		fc_response_type[80];
			int			fc_i;		/* 索引到*response_type */

			fc_cursor++;			/* 越过冒号 */
			while (pg_isblank(*fc_cursor))
				fc_cursor++;		/* 跳过空格 */
			fc_i = 0;
			while (*fc_cursor != ':' && *fc_cursor != '\r' && !pg_isblank(*fc_cursor) &&
				   fc_i < (int) (sizeof(fc_response_type) - 1))
				fc_response_type[fc_i++] = *fc_cursor++;
			fc_response_type[fc_i] = '\0';
			while (pg_isblank(*fc_cursor))
				fc_cursor++;		/* 跳过空格 */
			if (strcmp(fc_response_type, "USERID") != 0)
				return false;
			else
			{
				/*
				 * 这是一个USERID响应。很好。“光标”应该指向
				 * 操作系统类型之前的冒号。
				 */
				if (*fc_cursor != ':')
					return false;
				else
				{
					fc_cursor++;	/* 越过冒号 */
					/* 跳过操作系统字段。 */
					while (*fc_cursor != ':' && *fc_cursor != '\r')
						fc_cursor++;
					if (*fc_cursor != ':')
						return false;
					else
					{
						int			fc_i;	/* 索引到*ident_user */

						fc_cursor++;	/* 越过冒号 */
						while (pg_isblank(*fc_cursor))
							fc_cursor++;	/* 跳过空格 */
						/* 剩余行是用户名。复制它。 */
						fc_i = 0;
						while (*fc_cursor != '\r' && fc_i < IDENT_USERNAME_MAX)
							fc_ident_user[fc_i++] = *fc_cursor++;
						fc_ident_user[fc_i] = '\0';
						return true;
					}
				}
			}
		}
	}
}


/*
 *	与"remote_addr"上的ident服务器通信，找出
 *	哪个tcp连接属于"local_addr"
 *	如果成功检索到用户名，则检查用户映射。
 *
 *	XXX: 使用WaitLatchOrSocket()并在设置了闩锁时执行CHECK_FOR_INTERRUPTS()
 *	将提高对超时/取消的响应能力。
 */
static int fc_ident_inet(hbaPort *fc_port)
{
	const SockAddr fc_remote_addr = fc_port->raddr;
	const SockAddr fc_local_addr = fc_port->laddr;
	char		fc_ident_user[IDENT_USERNAME_MAX + 1];
	pgsocket	fc_sock_fd = PGINVALID_SOCKET; /* 用于与Ident服务器通信 */
	int			fc_rc;				/* 从本地调用函数返回的代码 */
	bool		fc_ident_return;
	char		fc_remote_addr_s[NI_MAXHOST];
	char		fc_remote_port[NI_MAXSERV];
	char		fc_local_addr_s[NI_MAXHOST];
	char		fc_local_port[NI_MAXSERV];
	char		fc_ident_port[NI_MAXSERV];
	char		fc_ident_query[80];
	char		fc_ident_response[80 + IDENT_USERNAME_MAX];
	struct addrinfo *fc_ident_serv = NULL,
			   *fc_la = NULL,
				fc_hints;

	/*
	 * 可能看起来有点奇怪，先转换为文本，然后再转换为
	 * sockaddr，但这与协议无关。
	 */
	pg_getnameinfo_all(&fc_remote_addr.addr, fc_remote_addr.salen,
					   fc_remote_addr_s, sizeof(fc_remote_addr_s),
					   fc_remote_port, sizeof(fc_remote_port),
					   NI_NUMERICHOST | NI_NUMERICSERV);
	pg_getnameinfo_all(&fc_local_addr.addr, fc_local_addr.salen,
					   fc_local_addr_s, sizeof(fc_local_addr_s),
					   fc_local_port, sizeof(fc_local_port),
					   NI_NUMERICHOST | NI_NUMERICSERV);

	snprintf(fc_ident_port, sizeof(fc_ident_port), "%d", IDENT_PORT);
	fc_hints.ai_flags = AI_NUMERICHOST;
	fc_hints.ai_family = fc_remote_addr.addr.ss_family;
	fc_hints.ai_socktype = SOCK_STREAM;
	fc_hints.ai_protocol = 0;
	fc_hints.ai_addrlen = 0;
	fc_hints.ai_canonname = NULL;
	fc_hints.ai_addr = NULL;
	fc_hints.ai_next = NULL;
	fc_rc = pg_getaddrinfo_all(fc_remote_addr_s, fc_ident_port, &fc_hints, &fc_ident_serv);
	if (fc_rc || !fc_ident_serv)
	{
		/* 我们不期望这种情况发生 */
		fc_ident_return = false;
		goto ident_inet_done;
	}

	fc_hints.ai_flags = AI_NUMERICHOST;
	fc_hints.ai_family = fc_local_addr.addr.ss_family;
	fc_hints.ai_socktype = SOCK_STREAM;
	fc_hints.ai_protocol = 0;
	fc_hints.ai_addrlen = 0;
	fc_hints.ai_canonname = NULL;
	fc_hints.ai_addr = NULL;
	fc_hints.ai_next = NULL;
	fc_rc = pg_getaddrinfo_all(fc_local_addr_s, NULL, &fc_hints, &fc_la);
	if (fc_rc || !fc_la)
	{
		/* 我们不期望这种情况发生 */
		fc_ident_return = false;
		goto ident_inet_done;
	}

	fc_sock_fd = socket(fc_ident_serv->ai_family, fc_ident_serv->ai_socktype,
					 fc_ident_serv->ai_protocol);
	if (fc_sock_fd == PGINVALID_SOCKET)
	{
		ereport(LOG,
				(errcode_for_socket_access(),
				 errmsg("could not create socket for Ident connection: %m")));
		fc_ident_return = false;
		goto ident_inet_done;
	}

	/*
	 * 绑定到客户端最初联系的地址，否则
	 * ident服务器将无法正确匹配连接。如果
	 * PostgreSQL服务器在IP别名上运行，这是必要的。
	 */
	fc_rc = bind(fc_sock_fd, fc_la->ai_addr, fc_la->ai_addrlen);
	if (fc_rc != 0)
	{
		ereport(LOG,
				(errcode_for_socket_access(),
				 errmsg("could not bind to local address \"%s\": %m",
						fc_local_addr_s)));
		fc_ident_return = false;
		goto ident_inet_done;
	}

	fc_rc = connect(fc_sock_fd, fc_ident_serv->ai_addr,
				 fc_ident_serv->ai_addrlen);
	if (fc_rc != 0)
	{
		ereport(LOG,
				(errcode_for_socket_access(),
				 errmsg("could not connect to Ident server at address \"%s\", port %s: %m",
						fc_remote_addr_s, fc_ident_port)));
		fc_ident_return = false;
		goto ident_inet_done;
	}

	/* 我们发送到Ident服务器的查询 */
	snprintf(fc_ident_query, sizeof(fc_ident_query), "%s,%s\r\n",
			 fc_remote_port, fc_local_port);

	/* 循环以防发送被中断 */
	do
	{
		CHECK_FOR_INTERRUPTS();

		fc_rc = send(fc_sock_fd, fc_ident_query, strlen(fc_ident_query), 0);
	} while (fc_rc < 0 && errno == EINTR);

	if (fc_rc < 0)
	{
		ereport(LOG,
				(errcode_for_socket_access(),
				 errmsg("could not send query to Ident server at address \"%s\", port %s: %m",
						fc_remote_addr_s, fc_ident_port)));
		fc_ident_return = false;
		goto ident_inet_done;
	}

	do
	{
		CHECK_FOR_INTERRUPTS();

		fc_rc = recv(fc_sock_fd, fc_ident_response, sizeof(fc_ident_response) - 1, 0);
	} while (fc_rc < 0 && errno == EINTR);

	if (fc_rc < 0)
	{
		ereport(LOG,
				(errcode_for_socket_access(),
				 errmsg("could not receive response from Ident server at address \"%s\", port %s: %m",
						fc_remote_addr_s, fc_ident_port)));
		fc_ident_return = false;
		goto ident_inet_done;
	}

	fc_ident_response[fc_rc] = '\0';
	fc_ident_return = fc_interpret_ident_response(fc_ident_response, fc_ident_user);
	if (!fc_ident_return)
		ereport(LOG,
				(errmsg("invalidly formatted response from Ident server: \"%s\"",
						fc_ident_response)));

ident_inet_done:
	if (fc_sock_fd != PGINVALID_SOCKET)
		closesocket(fc_sock_fd);
	if (fc_ident_serv)
		pg_freeaddrinfo_all(fc_remote_addr.addr.ss_family, fc_ident_serv);
	if (fc_la)
		pg_freeaddrinfo_all(fc_local_addr.addr.ss_family, fc_la);

	if (fc_ident_return)
	{
		/*
		 * 成功！存储身份，然后检查用户映射。注意
		 * 设置经过身份验证的身份在检查用户映射之前完成，
		 * 因为此时认证已经成功。
		 */
		fc_set_authn_id(fc_port, fc_ident_user);
		return check_usermap(fc_port->hba->usermap, fc_port->user_name, fc_ident_user, false);
	}
	return STATUS_ERROR;
}


/*----------------------------------------------------------------
 * 对等认证系统
 *----------------------------------------------------------------
 */

/*
 * 询问内核有关连接进程的凭据，
 * 确定相应用户的符号名称，并检查
 * 是否根据用户映射有效。
 *
 * 只有在授权的情况下，返回 STATUS_OK，否则返回 STATUS_ERROR。
 */
static int fc_auth_peer(hbaPort *fc_port)
{
	uid_t		fc_uid;
	gid_t		fc_gid;
#ifndef WIN32
	struct passwd *fc_pw;
	int			fc_ret;
#endif

	if (getpeereid(fc_port->sock, &fc_uid, &fc_gid) != 0)
	{
		/* 如果 getpeereid 是一个空函数，则提供特殊错误信息 */
		if (errno == ENOSYS)
			ereport(LOG,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("peer authentication is not supported on this platform")));
		else
			ereport(LOG,
					(errcode_for_socket_access(),
					 errmsg("could not get peer credentials: %m")));
		return STATUS_ERROR;
	}

#ifndef WIN32
	errno = 0;					/* 在调用之前清除 errno */
	fc_pw = getpwuid(fc_uid);
	if (!fc_pw)
	{
		int			fc_save_errno = errno;

		ereport(LOG,
				(errmsg("could not look up local user ID %ld: %s",
						(long) fc_uid,
						fc_save_errno ? strerror(fc_save_errno) : _("user does not exist"))));
		return STATUS_ERROR;
	}

	/*
	 * 复制静态 getpw*() 结果区域；这是我们的认证
	 * 身份。在调用 check_usermap 之前设置它，因为认证
	 * 已经成功，我们希望日志文件反映这一点。
	 */
	fc_set_authn_id(fc_port, fc_pw->pw_name);

	fc_ret = check_usermap(fc_port->hba->usermap, fc_port->user_name, fc_port->authn_id, false);

	return fc_ret;
#else
	/* 应该在上面以ENOSYS失败 */
	Assert(false);
	return STATUS_ERROR;
#endif
}


/*----------------------------------------------------------------
 * PAM 认证系统
 *----------------------------------------------------------------
 */
#ifdef USE_PAM

/*
 * PAM 对话函数
 */

static int fc_pam_passwd_conv_proc(int fc_num_msg, const struct pam_message **fc_msg,
					 struct pam_response **fc_resp, void *fc_appdata_ptr)
{
	const char *fc_passwd;
	struct pam_response *fc_reply;
	int			fc_i;

	if (fc_appdata_ptr)
		fc_passwd = (char *) fc_appdata_ptr;
	else
	{
		/*
		 * 针对 Solaris 2.6 的解决方法，其中 PAM 库损坏并且
		 * 不将 appdata_ptr 传递给对话例程
		 */
		fc_passwd = pam_passwd;
	}

	*fc_resp = NULL;				/* 在出现错误时退出 */

	if (fc_num_msg <= 0 || fc_num_msg > PAM_MAX_NUM_MSG)
		return PAM_CONV_ERR;

	/*
	 * 明确不在这里使用 palloc - PAM 会在
	 * pam_end() 中释放此内存
	 */
	if ((fc_reply = calloc(fc_num_msg, sizeof(struct pam_response))) == NULL)
	{
		ereport(LOG,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));
		return PAM_CONV_ERR;
	}

	for (fc_i = 0; fc_i < fc_num_msg; fc_i++)
	{
		switch (fc_msg[fc_i]->msg_style)
		{
			case PAM_PROMPT_ECHO_OFF:
				if (strlen(fc_passwd) == 0)
				{
					/*
					 * 密码第一次没有传递给 PAM -
					 * 让我们去询问客户端发送密码，
					 * 然后我们将其填入 PAM。
					 */
					sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD, NULL, 0);
					fc_passwd = fc_recv_password_packet(pam_port_cludge);
					if (fc_passwd == NULL)
					{
						/*
						 * 客户端不想发送密码。我们
						 * 有意不在此处或更高级别记录任何内容。
						 */
						pam_no_password = true;
						goto fail;
					}
				}
				if ((fc_reply[fc_i].resp = strdup(fc_passwd)) == NULL)
					goto fail;
				fc_reply[fc_i].resp_retcode = PAM_SUCCESS;
				break;
			case PAM_ERROR_MSG:
				ereport(LOG,
						(errmsg("error from underlying PAM layer: %s",
								fc_msg[fc_i]->msg)));
				/* FALL THROUGH */
			case PAM_TEXT_INFO:
				/* 我们不麻烦记录 TEXT_INFO 消息 */
				if ((fc_reply[fc_i].resp = strdup("")) == NULL)
					goto fail;
				fc_reply[fc_i].resp_retcode = PAM_SUCCESS;
				break;
			default:
				ereport(LOG,
						(errmsg("unsupported PAM conversation %d/\"%s\"",
								fc_msg[fc_i]->msg_style,
								fc_msg[fc_i]->msg ? fc_msg[fc_i]->msg : "(none)")));
				goto fail;
		}
	}

	*fc_resp = fc_reply;
	return PAM_SUCCESS;

fail:
	/* 释放我们分配的任何内容 */
	for (fc_i = 0; fc_i < fc_num_msg; fc_i++)
	{
		if (fc_reply[fc_i].resp != NULL)
			free(fc_reply[fc_i].resp);
	}
	free(fc_reply);

	return PAM_CONV_ERR;
}


/*
 * 检查与 PAM 的认证。
 */
static int fc_CheckPAMAuth(Port *fc_port, const char *fc_user, const char *fc_password)
{
	int			fc_retval;
	pam_handle_t *fc_pamh = NULL;

	/*
	 * 我们不能完全依赖 PAM 通过 appdata --- 它似乎
	 * 在至少 Solaris 2.6 上不起作用。因此使用这些丑陋的静态
	 * 变量代替。
	 */
	pam_passwd = fc_password;
	pam_port_cludge = fc_port;
	pam_no_password = false;

	/*
	 * 设置对话结构的应用数据部分。此部分
	 * 后来在 PAM 对话中用于将密码传递给
	 * 认证模块。
	 */
	pam_passw_conv.appdata_ptr = unconstify(char *, fc_password);	/* 来自上面的密码，
																 * 未分配 */

	/* 可选地，可以在 pg_hba.conf 中设置服务名称 */
	if (fc_port->hba->pamservice && fc_port->hba->pamservice[0] != '\0')
		fc_retval = pam_start(fc_port->hba->pamservice, "pgsql@",
						   &pam_passw_conv, &fc_pamh);
	else
		fc_retval = pam_start(PGSQL_PAM_SERVICE, "pgsql@",
						   &pam_passw_conv, &fc_pamh);

	if (fc_retval != PAM_SUCCESS)
	{
		ereport(LOG,
				(errmsg("could not create PAM authenticator: %s",
						pam_strerror(fc_pamh, fc_retval))));
		pam_passwd = NULL;		/* 取消设置 pam_passwd */
		return STATUS_ERROR;
	}

	fc_retval = pam_set_item(fc_pamh, PAM_USER, fc_user);

	if (fc_retval != PAM_SUCCESS)
	{
		ereport(LOG,
				(errmsg("pam_set_item(PAM_USER) failed: %s",
						pam_strerror(fc_pamh, fc_retval))));
		pam_passwd = NULL;		/* 取消设置 pam_passwd */
		return STATUS_ERROR;
	}

	if (fc_port->hba->conntype != ctLocal)
	{
		char		fc_hostinfo[NI_MAXHOST];
		int			fc_flags;

		if (fc_port->hba->pam_use_hostname)
			fc_flags = 0;
		else
			fc_flags = NI_NUMERICHOST | NI_NUMERICSERV;

		fc_retval = pg_getnameinfo_all(&fc_port->raddr.addr, fc_port->raddr.salen,
									fc_hostinfo, sizeof(fc_hostinfo), NULL, 0,
									fc_flags);
		if (fc_retval != 0)
		{
			ereport(WARNING,
					(errmsg_internal("pg_getnameinfo_all() failed: %s",
									 gai_strerror(fc_retval))));
			return STATUS_ERROR;
		}

		fc_retval = pam_set_item(fc_pamh, PAM_RHOST, fc_hostinfo);

		if (fc_retval != PAM_SUCCESS)
		{
			ereport(LOG,
					(errmsg("pam_set_item(PAM_RHOST) failed: %s",
							pam_strerror(fc_pamh, fc_retval))));
			pam_passwd = NULL;
			return STATUS_ERROR;
		}
	}

	fc_retval = pam_set_item(fc_pamh, PAM_CONV, &pam_passw_conv);

	if (fc_retval != PAM_SUCCESS)
	{
		ereport(LOG,
				(errmsg("pam_set_item(PAM_CONV) failed: %s",
						pam_strerror(fc_pamh, fc_retval))));
		pam_passwd = NULL;		/* 取消设置 pam_passwd */
		return STATUS_ERROR;
	}

	fc_retval = pam_authenticate(fc_pamh, 0);

	if (fc_retval != PAM_SUCCESS)
	{
		/* 如果 pam_passwd_conv_proc 看到 EOF，不要记录任何内容 */
		if (!pam_no_password)
			ereport(LOG,
					(errmsg("pam_authenticate failed: %s",
							pam_strerror(fc_pamh, fc_retval))));
		pam_passwd = NULL;		/* 取消设置 pam_passwd */
		return pam_no_password ? STATUS_EOF : STATUS_ERROR;
	}

	fc_retval = pam_acct_mgmt(fc_pamh, 0);

	if (fc_retval != PAM_SUCCESS)
	{
		/* 如果 pam_passwd_conv_proc 看到 EOF，不要记录任何内容 */
		if (!pam_no_password)
			ereport(LOG,
					(errmsg("pam_acct_mgmt failed: %s",
							pam_strerror(fc_pamh, fc_retval))));
		pam_passwd = NULL;		/* 取消设置 pam_passwd */
		return pam_no_password ? STATUS_EOF : STATUS_ERROR;
	}

	fc_retval = pam_end(fc_pamh, fc_retval);

	if (fc_retval != PAM_SUCCESS)
	{
		ereport(LOG,
				(errmsg("could not release PAM authenticator: %s",
						pam_strerror(fc_pamh, fc_retval))));
	}

	pam_passwd = NULL;			/* 取消设置 pam_passwd */

	if (fc_retval == PAM_SUCCESS)
		fc_set_authn_id(fc_port, fc_user);

	return (fc_retval == PAM_SUCCESS ? STATUS_OK : STATUS_ERROR);
}
#endif							/* USE_PAM */


/*----------------------------------------------------------------
 * BSD 认证系统
 *----------------------------------------------------------------
 */
#ifdef USE_BSD_AUTH
static int fc_CheckBSDAuth(Port *fc_port, char *fc_user)
{
	char	   *fc_passwd;
	int			fc_retval;

	/* 发送常规密码请求给客户端，并获取响应 */
	sendAuthRequest(fc_port, AUTH_REQ_PASSWORD, NULL, 0);

	fc_passwd = recv_password_packet(fc_port);
	if (fc_passwd == NULL)
		return STATUS_EOF;

	/*
	 * 请求 BSD 认证系统验证密码。请注意 auth_userokay
	 * 会用零覆盖密码字符串，但这只是一个
	 * 临时字符串，所以我们不在乎。
	 */
	fc_retval = auth_userokay(fc_user, NULL, "auth-postgresql", fc_passwd);

	pfree(fc_passwd);

	if (!fc_retval)
		return STATUS_ERROR;

	set_authn_id(fc_port, fc_user);
	return STATUS_OK;
}
#endif							/* USE_BSD_AUTH */


/*----------------------------------------------------------------
 * LDAP 认证系统
 *----------------------------------------------------------------
 */
/*
 * 初始化与 LDAP 服务器的连接，包括根据需要设置
 * TLS。
 */
/*
	 * OpenLDAP 提供了一个非标准扩展 ldap_initialize()，它接受
	 * 一组 URI，允许我们请求 "ldaps" 而不是 "ldap"。它
	 * 还提供了 ldap_domain2hostlist() 以便使用 DNS SRV 自动查找
	 * LDAP 服务器。它们在同一版本中引入，因此目前我们
	 * 不会对后者进行额外的配置检查。
	 */
/* 我们将在这里构建一个用空格分隔的 scheme://hostname:port 列表 */
/*
		 * 如果 pg_hba.conf 没有提供主机名，我们可以请求 OpenLDAP 尝试
		 * 通过从基 DN 提取域名并查找 _ldap._tcp.<domain> 的 DNS SRV 记录来查找一些。
		 */
/*
		 * 需要重新初始化LDAP连接，以便我们可以使用
		 * 不同的用户名进行绑定。
		 */
/* 查找 LDAP 服务器主机和端口号的列表 */
/* 我们有一个用空格分隔的 host:port 条目列表 */
/* 我们有一个来自 pg_hba.conf 的用空格分隔的主机列表 */
/* 将 host[:port] 条目列表转换为完整的 URI */
/* 查找下一个条目的跨度 */
/* 如果这不是第一个 URI，则附加一个空格分隔符 */
/* 附加 scheme://host:port */
/* 跃过此条目和任意数量的尾随空格 */
/* 如果我们查找了 SRV 记录，则从 OpenLDAP 中释放内存 */
/* 最后，尝试使用 URI 列表连接 */
/*
			 * 需要动态加载此函数，因为它可能在 Windows 上不存在，
			 * 参考时会导致整个 exe 加载错误。
			 */
/*
				 * 由于我们从 wldap32 导入了其他文件，这种情况
				 * 不应该发生，但仍然检查一下
				 */
/*
			 * 故意泄漏 LDAP 句柄，因为我们需要库保持打开状态。
			 * 这没问题，因为每个进程只会泄漏一次，并在进程退出时自动清理。
			 */
/* FormatSearchFilter 识别的占位符。 目前只有一个。 */
/* 并非所有 LDAP 实现都定义这个。 */
/* 并非所有 LDAP 实现都定义这个。 */
/*
 * 返回一个新分配的 C 字符串，该字符串复制自 "pattern"，并将所有
 * 占位符 "$username" 替换为 "user_name"。
 */
/*
 * 执行 LDAP 认证
 */
/*
	 * 对于 OpenLDAP，如果我们有基 DN，则允许空的主机名。我们将通过
	 * OpenLDAP 库功能查找带有 DNS SRV 记录的服务器。
	 */
/*
	 * 如果我们使用 SRV 记录，则没有服务器名称，因此我们将在错误
	 * 消息中显示一个空字符串。
	 */
/* 客户端不会发送密码 */
/* 错误消息已发送 */
/*
		 * 首先执行 LDAP 搜索以查找我们尝试以登录用户的 DN。
		 */
#ifdef USE_LDAP

static int	fc_errdetail_for_ldap(LDAP *fc_ldap);

/*
		 * 不允许任何我们需要转义的字符，
		 * 因为它们在用户名中并不合理。允许它们将使
		 * 注入任何类型的自定义过滤器成为可能。
		 */
static int fc_InitializeLDAPConnection(Port *fc_port, LDAP **fc_ldap)
{
	const char *fc_scheme;
	int			fc_ldapversion = LDAP_VERSION3;
	int			fc_r;

	fc_scheme = fc_port->hba->ldapscheme;
	if (fc_scheme == NULL)
		fc_scheme = "ldap";
#ifdef WIN32
	if (strcmp(scheme, "ldaps") == 0)
		*ldap = ldap_sslinit(port->hba->ldapserver, port->hba->ldapport, 1);
	else
		*ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
	if (!*ldap)
	{
		ereport(LOG,
				(errmsg("could not initialize LDAP: error code %d",
						(int) LdapGetLastError())));

		return STATUS_ERROR;
	}
#else
#ifdef HAVE_LDAP_INITIALIZE

	/*
		 * 绑定预定义的用户名/密码（如果可用）用于
		 * 搜索。如果未指定，这将变为匿名绑定。
		 */
	{
		StringInfoData fc_uris;
		char	   *fc_hostlist = NULL;
		char	   *fc_p;
		bool		fc_append_port;

		/* 构建自定义过滤器还是单一属性过滤器？ */
		initStringInfo(&fc_uris);

		/* 解除绑定并断开与LDAP服务器的连接 */
		if (!fc_port->hba->ldapserver || fc_port->hba->ldapserver[0] == '\0')
		{
			char	   *fc_domain;

			/*
		 * 需要重新初始化LDAP连接，以便我们可以使用
		 * 不同的用户名进行绑定。
		 */
			if (ldap_dn2domain(fc_port->hba->ldapbasedn, &fc_domain))
			{
				ereport(LOG,
						(errmsg("could not extract domain name from ldapbasedn")));
				return STATUS_ERROR;
			}

			/* 错误消息已经发送 */
			if (ldap_domain2hostlist(fc_domain, &fc_hostlist))
			{
				ereport(LOG,
						(errmsg("LDAP authentication could not find DNS SRV records for \"%s\"",
								fc_domain),
						 (errhint("Set an LDAP server name explicitly."))));
				ldap_memfree(fc_domain);
				return STATUS_ERROR;
			}
			ldap_memfree(fc_domain);

			/* 将原始绑定DN保存为经过验证的身份。 */
			fc_p = fc_hostlist;
			fc_append_port = false;
		}
		else
		{
			/*
 * 如果可以从LDAP '诊断消息'构造，添加详细错误消息文本
 * 到当前错误中。
 */
			fc_p = fc_port->hba->ldapserver;
			fc_append_port = true;
		}

		/* USE_LDAP */
		do
		{
			size_t		fc_size;

			/*----------------------------------------------------------------
 * SSL客户端证书认证
 *----------------------------------------------------------------
 */
			fc_size = strcspn(fc_p, " ");

			/* 选择要比较的正确字段 */
			if (fc_uris.len > 0)
				appendStringInfoChar(&fc_uris, ' ');

			/* 确保我们在证书中收到了用户名 */
			appendStringInfoString(&fc_uris, fc_scheme);
			appendStringInfoString(&fc_uris, "://");
			appendBinaryStringInfo(&fc_uris, fc_p, fc_size);
			if (fc_append_port)
				appendStringInfo(&fc_uris, ":%d", fc_port->hba->ldapport);

			/*
		 * 对于证书认证，客户端的主题DN始终是我们的经过验证
		 * 身份，即使我们只是使用其CN进行授权。现在设置
		 * 它，而不是等待下面的check_usermap()，因为
		 * 认证已经成功，我们希望日志文件能够反映出来。
		 */
			fc_p += fc_size;
			while (*fc_p == ' ')
				++fc_p;
		} while (*fc_p);

		/*
			 * 这不应该发生，因为在这个上下文中peer_dn和peer_cn应该
			 * 已经设置。
			 */
		if (fc_hostlist)
			ldap_memfree(fc_hostlist);

		/* 只是将证书cn/dn传递给用户映射检查 */
		fc_r = ldap_initialize(fc_ldap, fc_uris.data);
		pfree(fc_uris.data);
		if (fc_r != LDAP_SUCCESS)
		{
			ereport(LOG,
					(errmsg("could not initialize LDAP: %s",
							ldap_err2string(fc_r))));

			return STATUS_ERROR;
		}
	}
#else
	if (strcmp(scheme, "ldaps") == 0)
	{
		ereport(LOG,
				(errmsg("ldaps not supported with this LDAP library")));

		return STATUS_ERROR;
	}
	*ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
	if (!*ldap)
	{
		ereport(LOG,
				(errmsg("could not initialize LDAP: %m")));

		return STATUS_ERROR;
	}
#endif
#endif

	if ((fc_r = ldap_set_option(*fc_ldap, LDAP_OPT_PROTOCOL_VERSION, &fc_ldapversion)) != LDAP_SUCCESS)
	{
		ereport(LOG,
				(errmsg("could not set LDAP protocol version: %s",
						ldap_err2string(fc_r)),
				 fc_errdetail_for_ldap(*fc_ldap)));
		ldap_unbind(*fc_ldap);
		return STATUS_ERROR;
	}

	if (fc_port->hba->ldaptls)
	{
#ifndef WIN32
		if ((fc_r = ldap_start_tls_s(*fc_ldap, NULL, NULL)) != LDAP_SUCCESS)
#else
		static __ldap_start_tls_sA _ldap_start_tls_sA = NULL;

		if (_ldap_start_tls_sA == NULL)
		{
			/*
		 * 如果指定了clientcert=verify-full并且认证
		 * 方法不是uaCert，则记录拒绝认证的原因。
		 */
			HANDLE		ldaphandle;

			ldaphandle = LoadLibrary("WLDAP32.DLL");
			if (ldaphandle == NULL)
			{
				/*----------------------------------------------------------------
 * RADIUS认证
 *----------------------------------------------------------------
 */
				ereport(LOG,
						(errmsg("could not load library \"%s\": error code %lu",
								"WLDAP32.DLL", GetLastError())));
				ldap_unbind(*ldap);
				return STATUS_ERROR;
			}
			_ldap_start_tls_sA = (__ldap_start_tls_sA) (pg_funcptr_t) GetProcAddress(ldaphandle, "ldap_start_tls_sA");
			if (_ldap_start_tls_sA == NULL)
			{
				ereport(LOG,
						(errmsg("could not load function _ldap_start_tls_sA in wldap32.dll"),
						 errdetail("LDAP over SSL is not supported on this platform.")));
				ldap_unbind(*ldap);
				FreeLibrary(ldaphandle);
				return STATUS_ERROR;
			}

			/*
 * RADIUS认证在RFC2865（以及其他几个文档）中进行了描述。
 */
		}
		if ((r = _ldap_start_tls_sA(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
#endif
		{
			ereport(LOG,
					(errmsg("could not start LDAP TLS session: %s",
							ldap_err2string(fc_r)),
					 fc_errdetail_for_ldap(*fc_ldap)));
			ldap_unbind(*fc_ldap);
			return STATUS_ERROR;
		}
	}

	return STATUS_OK;
}

/* 我们将创建或接受的RADIUS数据包的最大大小 */
#define LPH_USERNAME "$username"
#define LPH_USERNAME_LEN (sizeof(LPH_USERNAME) - 1)

/* 这比严格必要的要长一点： */
#ifndef LDAP_NO_ATTRS
#define LDAP_NO_ATTRS "1.1"
#endif

/* 这比严格必要的要长一点： */
#ifndef LDAPS_PORT
#define LDAPS_PORT 636
#endif

/* RADIUS属性 */
static char * fc_FormatSearchFilter(const char *fc_pattern, const char *fc_user_name)
{
	StringInfoData fc_output;

	initStringInfo(&fc_output);
	while (*fc_pattern != '\0')
	{
		if (strncmp(fc_pattern, LPH_USERNAME, LPH_USERNAME_LEN) == 0)
		{
			appendStringInfoString(&fc_output, fc_user_name);
			fc_pattern += LPH_USERNAME_LEN;
		}
		else
			appendStringInfoChar(&fc_output, *fc_pattern++);
	}

	return fc_output.data;
}

/* RADIUS服务类型 */
static int fc_CheckLDAPAuth(Port *fc_port)
{
	char	   *fc_passwd;
	LDAP	   *fc_ldap;
	int			fc_r;
	char	   *fc_fulluser;
	const char *fc_server_name;

#ifdef HAVE_LDAP_INITIALIZE

	/* 等待的秒数 - XXX：应该在配置变量中！ */
	if ((!fc_port->hba->ldapserver || fc_port->hba->ldapserver[0] == '\0') &&
		(!fc_port->hba->ldapbasedn || fc_port->hba->ldapbasedn[0] == '\0'))
	{
		ereport(LOG,
				(errmsg("LDAP server not specified, and no ldapbasedn")));
		return STATUS_ERROR;
	}
#else
	if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0')
	{
		ereport(LOG,
				(errmsg("LDAP server not specified")));
		return STATUS_ERROR;
	}
#endif

	/*
		 * 在真实的远程数据中，这绝对不会发生。但捕获它
		 * 以确保我们不会溢出缓冲区。我们将跳过添加
		 * 损坏的属性，这最终将导致认证失败。
		 */
	fc_server_name = fc_port->hba->ldapserver ? fc_port->hba->ldapserver : "";

	if (fc_port->hba->ldapport == 0)
	{
		if (fc_port->hba->ldapscheme != NULL &&
			strcmp(fc_port->hba->ldapscheme, "ldaps") == 0)
			fc_port->hba->ldapport = LDAPS_PORT;
		else
			fc_port->hba->ldapport = LDAP_PORT;
	}

	sendAuthRequest(fc_port, AUTH_REQ_PASSWORD, NULL, 0);

	fc_passwd = fc_recv_password_packet(fc_port);
	if (fc_passwd == NULL)
		return STATUS_EOF;		/* 客户端不会发送密码 */

	if (fc_InitializeLDAPConnection(fc_port, &fc_ldap) == STATUS_ERROR)
	{
		/* 确保结构对齐正确 */
		pfree(fc_passwd);
		return STATUS_ERROR;
	}

	if (fc_port->hba->ldapbasedn)
	{
		/* 验证参数 */
		char	   *fc_filter;
		LDAPMessage *fc_search_message;
		LDAPMessage *fc_entry;
		char	   *fc_attributes[] = {LDAP_NO_ATTRS, NULL};
		char	   *fc_dn;
		char	   *fc_c;
		int			fc_count;

		/* 向客户端发送常规密码请求，并获取响应 */
		for (fc_c = fc_port->user_name; *fc_c; fc_c++)
		{
			if (*fc_c == '*' ||
				*fc_c == '(' ||
				*fc_c == ')' ||
				*fc_c == '\\' ||
				*fc_c == '/')
			{
				ereport(LOG,
						(errmsg("invalid character in user name for LDAP authentication")));
				ldap_unbind(fc_ldap);
				pfree(fc_passwd);
				return STATUS_ERROR;
			}
		}

		/* 客户端不会发送密码 */
		fc_r = ldap_simple_bind_s(fc_ldap,
							   fc_port->hba->ldapbinddn ? fc_port->hba->ldapbinddn : "",
							   fc_port->hba->ldapbindpasswd ? fc_port->hba->ldapbindpasswd : "");
		if (fc_r != LDAP_SUCCESS)
		{
			ereport(LOG,
					(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
							fc_port->hba->ldapbinddn ? fc_port->hba->ldapbinddn : "",
							fc_server_name,
							ldap_err2string(fc_r)),
					 fc_errdetail_for_ldap(fc_ldap)));
			ldap_unbind(fc_ldap);
			pfree(fc_passwd);
			return STATUS_ERROR;
		}

		/*
	 * 按顺序循环并尝试每个服务器。
	 */
		if (fc_port->hba->ldapsearchfilter)
			fc_filter = fc_FormatSearchFilter(fc_port->hba->ldapsearchfilter, fc_port->user_name);
		else if (fc_port->hba->ldapsearchattribute)
			fc_filter = psprintf("(%s=%s)", fc_port->hba->ldapsearchattribute, fc_port->user_name);
		else
			fc_filter = psprintf("(uid=%s)", fc_port->user_name);

		fc_r = ldap_search_s(fc_ldap,
						  fc_port->hba->ldapbasedn,
						  fc_port->hba->ldapscope,
						  fc_filter,
						  fc_attributes,
						  0,
						  &fc_search_message);

		if (fc_r != LDAP_SUCCESS)
		{
			ereport(LOG,
					(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
							fc_filter, fc_server_name, ldap_err2string(fc_r)),
					 fc_errdetail_for_ldap(fc_ldap)));
			ldap_unbind(fc_ldap);
			pfree(fc_passwd);
			pfree(fc_filter);
			return STATUS_ERROR;
		}

		fc_count = ldap_count_entries(fc_ldap, fc_search_message);
		if (fc_count != 1)
		{
			if (fc_count == 0)
				ereport(LOG,
						(errmsg("LDAP user \"%s\" does not exist", fc_port->user_name),
						 errdetail("LDAP search for filter \"%s\" on server \"%s\" returned no entries.",
								   fc_filter, fc_server_name)));
			else
				ereport(LOG,
						(errmsg("LDAP user \"%s\" is not unique", fc_port->user_name),
						 errdetail_plural("LDAP search for filter \"%s\" on server \"%s\" returned %d entry.",
										  "LDAP search for filter \"%s\" on server \"%s\" returned %d entries.",
										  fc_count,
										  fc_filter, fc_server_name, fc_count)));

			ldap_unbind(fc_ldap);
			pfree(fc_passwd);
			pfree(fc_filter);
			ldap_msgfree(fc_search_message);
			return STATUS_ERROR;
		}

		fc_entry = ldap_first_entry(fc_ldap, fc_search_message);
		fc_dn = ldap_get_dn(fc_ldap, fc_entry);
		if (fc_dn == NULL)
		{
			int			fc_error;

			(void) ldap_get_option(fc_ldap, LDAP_OPT_ERROR_NUMBER, &fc_error);
			ereport(LOG,
					(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
							fc_filter, fc_server_name,
							ldap_err2string(fc_error)),
					 fc_errdetail_for_ldap(fc_ldap)));
			ldap_unbind(fc_ldap);
			pfree(fc_passwd);
			pfree(fc_filter);
			ldap_msgfree(fc_search_message);
			return STATUS_ERROR;
		}
		fc_fulluser = pstrdup(fc_dn);

		pfree(fc_filter);
		ldap_memfree(fc_dn);
		ldap_msgfree(fc_search_message);

		/*------
		 * STATUS_OK = 登录成功
		 * STATUS_ERROR = 登录不成功，但尝试下一个服务器
		 * STATUS_EOF = 登录不成功，并且不尝试下一个服务器
		 *------
		 */
		fc_r = ldap_unbind_s(fc_ldap);
		if (fc_r != LDAP_SUCCESS)
		{
			ereport(LOG,
					(errmsg("could not unbind after searching for user \"%s\" on server \"%s\"",
							fc_fulluser, fc_server_name)));
			pfree(fc_passwd);
			pfree(fc_fulluser);
			return STATUS_ERROR;
		}

		/*
		 * secret、port 和 identifiers 的长度可以为 0（使用默认值），
		 * 长度为 1（在每个地方使用相同的值）或与服务器的长度相同。
		 * 因此，如果长度 >1，我们向前推进一步。在其他情况下，我们
		 * 不推进，并将重用正确的值。
		 */
		if (fc_InitializeLDAPConnection(fc_port, &fc_ldap) == STATUS_ERROR)
		{
			pfree(fc_passwd);
			pfree(fc_fulluser);

			/* 确保结构对齐正确 */
			return STATUS_ERROR;
		}
	}
	else
		fc_fulluser = psprintf("%s%s%s",
							fc_port->hba->ldapprefix ? fc_port->hba->ldapprefix : "",
							fc_port->user_name,
							fc_port->hba->ldapsuffix ? fc_port->hba->ldapsuffix : "");

	fc_r = ldap_simple_bind_s(fc_ldap, fc_fulluser, fc_passwd);

	if (fc_r != LDAP_SUCCESS)
	{
		ereport(LOG,
				(errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s",
						fc_fulluser, fc_server_name, ldap_err2string(fc_r)),
				 fc_errdetail_for_ldap(fc_ldap)));
		ldap_unbind(fc_ldap);
		pfree(fc_passwd);
		pfree(fc_fulluser);
		return STATUS_ERROR;
	}

	/* 赋值默认值 */
	fc_set_authn_id(fc_port, fc_fulluser);

	ldap_unbind(fc_ldap);
	pfree(fc_passwd);
	pfree(fc_fulluser);

	return STATUS_OK;
}

/* XXX: 添加对返回多个地址的支持？ */
static int fc_errdetail_for_ldap(LDAP *fc_ldap)
{
	char	   *fc_message;
	int			fc_rc;

	fc_rc = ldap_get_option(fc_ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &fc_message);
	if (fc_rc == LDAP_SUCCESS && fc_message != NULL)
	{
		errdetail("LDAP diagnostics: %s", fc_message);
		ldap_memfree(fc_message);
	}

	return 0;
}

#endif							/* USE_LDAP */


/*
	 * RADIUS 密码属性计算为：e[0] = p[0] XOR
	 * MD5(secret + Request Authenticator) 对于第一个 16 字节组，
	 * 然后：e[i] = p[i] XOR MD5(secret + e[i-1]) 对于后续的
	 * （如果必要）
	 */
#ifdef USE_SSL
static int fc_CheckCertAuth(Port *fc_port)
{
	int			fc_status_check_usermap = STATUS_ERROR;
	char	   *fc_peer_username = NULL;

	Assert(fc_port->ssl);

	/* 在第一次迭代中，我们使用 Request Authenticator 向量 */
	switch (fc_port->hba->clientcertname)
	{
		case clientCertDN:
			fc_peer_username = fc_port->peer_dn;
			break;
		case clientCertCN:
			fc_peer_username = fc_port->peer_cn;
	}

	/*
		 * .. 在后续迭代中使用上一个 XOR 的结果
		 * （在下面计算）
		 */
	if (fc_peer_username == NULL ||
		strlen(fc_peer_username) <= 0)
	{
		ereport(LOG,
				(errmsg("certificate authentication failed for user \"%s\": client certificate contains no user name",
						fc_port->user_name)));
		return STATUS_ERROR;
	}

	if (fc_port->hba->auth_method == uaCert)
	{
		/* 长度需要在网络上传输时按照网络顺序 */
		if (!fc_port->peer_dn)
		{
			/* 不再需要服务器地址 */
			ereport(LOG,
					(errmsg("certificate authentication failed for user \"%s\": unable to retrieve subject DN",
							fc_port->user_name)));
			return STATUS_ERROR;
		}

		fc_set_authn_id(fc_port, fc_port->peer_dn);
	}

	/*
	 * 确定我们应该在何时超时。我们不能仅仅使用单个
	 * 带超时的 select() 调用，因为有人可能会向我们的端口发送无效的
	 * 数据包，从而导致我们在循环中重试并且永远不会超时。
	 *
	 * XXX: 使用 WaitLatchOrSocket() 并在
	 * latch 被设置时进行 CHECK_FOR_INTERRUPTS()，将改善
	 * 对超时/取消的响应能力。
	 */
	fc_status_check_usermap = check_usermap(fc_port->hba->usermap, fc_port->user_name, fc_peer_username, false);
	if (fc_status_check_usermap != STATUS_OK)
	{
		/* 其他任何情况都是实际错误 */
		if (fc_port->hba->clientcert == clientCertFull && fc_port->hba->auth_method != uaCert)
		{
			switch (fc_port->hba->clientcertname)
			{
				case clientCertDN:
					ereport(LOG,
							(errmsg("certificate validation (clientcert=verify-full) failed for user \"%s\": DN mismatch",
									fc_port->user_name)));
					break;
				case clientCertCN:
					ereport(LOG,
							(errmsg("certificate validation (clientcert=verify-full) failed for user \"%s\": CN mismatch",
									fc_port->user_name)));
			}
		}
	}
	return fc_status_check_usermap;
}
#endif


/*
		 * 尝试读取响应数据包，并验证内容。
		 *
		 * 任何实际上不是 RADIUS 数据包，或以其他方式不
		 * 验证为明确拒绝的数据包，都将被忽略，我们将重试
		 * 另一个数据包（直到达到超时）。这样做是为了避免
		 * 通过在我们期望接收 RADIUS 响应的端口上泛洪无效
		 * 数据包而导致拒绝服务的可能性。
		 */

/*
		 * 验证响应验证器，计算方式为
		 * MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
		 */

#define RADIUS_VECTOR_LENGTH 16
#define RADIUS_HEADER_LENGTH 20
#define RADIUS_MAX_PASSWORD_LENGTH 128

/* code+id+length */
#define RADIUS_BUFFER_SIZE 1024

typedef struct
{
	uint8		attribute;
	uint8		length;
	uint8		data[FLEXIBLE_ARRAY_MEMBER];
} radius_attribute;

typedef struct
{
	uint8		code;
	uint8		id;
	uint16		length;
	uint8		vector[RADIUS_VECTOR_LENGTH];
	/* 请求
																		 * 验证器，来自
																		 * 原始数据包 */
	char		pad[RADIUS_BUFFER_SIZE - RADIUS_VECTOR_LENGTH];
} radius_packet;

/* 可能根本没有
													 * 属性 */
#define RADIUS_ACCESS_REQUEST	1
#define RADIUS_ACCESS_ACCEPT	2
#define RADIUS_ACCESS_REJECT	3

/* while (true) */
#define RADIUS_USER_NAME		1
#define RADIUS_PASSWORD			2
#define RADIUS_SERVICE_TYPE		6
#define RADIUS_NAS_IDENTIFIER	32

//#ifdef FDD
#define RADIUS_AUTHENTICATE_ONLY	8

//返回STATUS_EOF代表客户端没有传密码过来，需要保持这个错误码，以备ClientAuthentication_hook中进行特殊处理
#define RADIUS_TIMEOUT 3

static void fc_radius_add_attribute(radius_packet *fc_packet, uint8 fc_type, const unsigned char *fc_data, int fc_len)
{
	radius_attribute *fc_attr;

	if (fc_packet->length + fc_len > RADIUS_BUFFER_SIZE)
	{
		//#endif
		elog(WARNING,
			 "adding attribute code %d with length %d to radius packet would create oversize packet, ignoring",
			 fc_type, fc_len);
		return;
	}

	fc_attr = (radius_attribute *) ((unsigned char *) fc_packet + fc_packet->length);
	fc_attr->attribute = fc_type;
	fc_attr->length = fc_len + 2;		//cppcheck
	memcpy(fc_attr->data, fc_data, fc_len);
	fc_packet->length += fc_attr->length;
}

static int fc_CheckRADIUSAuth(Port *fc_port)
{
	char	   *fc_passwd;
	ListCell   *fc_server,
			   *fc_secrets,
			   *fc_radiusports,
			   *fc_identifiers;

	//cppcheck
	Assert(offsetof(radius_packet, vector) == 4);

	//hostname:port 列表在这里 */
	if (list_length(fc_port->hba->radiusservers) < 1)
	{
		ereport(LOG,
				(errmsg("RADIUS server not specified")));
		return STATUS_ERROR;
	}

	if (list_length(fc_port->hba->radiussecrets) < 1)
	{
		ereport(LOG,
				(errmsg("RADIUS secret not specified")));
		return STATUS_ERROR;
	}

	/* 发送常规密码请求给客户端，并获取响应 */
	sendAuthRequest(fc_port, AUTH_REQ_PASSWORD, NULL, 0);

	fc_passwd = fc_recv_password_packet(fc_port);
	if (fc_passwd == NULL)
		return STATUS_EOF;		/* 客户端不会发送密码 */

	if (strlen(fc_passwd) > RADIUS_MAX_PASSWORD_LENGTH)
	{
		ereport(LOG,
				(errmsg("RADIUS authentication does not support passwords longer than %d characters", RADIUS_MAX_PASSWORD_LENGTH)));
		pfree(fc_passwd);
		return STATUS_ERROR;
	}

	//cppcheck
	fc_secrets = list_head(fc_port->hba->radiussecrets);
	fc_radiusports = list_head(fc_port->hba->radiusports);
	fc_identifiers = list_head(fc_port->hba->radiusidentifiers);
	foreach(fc_server, fc_port->hba->radiusservers)
	{
		int			fc_ret = fc_PerformRadiusTransaction(lfirst(fc_server),
												   lfirst(fc_secrets),
												   fc_radiusports ? lfirst(fc_radiusports) : NULL,
												   fc_identifiers ? lfirst(fc_identifiers) : NULL,
												   fc_port->user_name,
												   fc_passwd);

		/*------
		 * STATUS_OK = Login OK
		 * STATUS_ERROR = Login not OK, but try next server
		 * STATUS_EOF = Login not OK, and don't try next server
		 *------
		 */
		if (fc_ret == STATUS_OK)
		{
			fc_set_authn_id(fc_port, fc_port->user_name);

			pfree(fc_passwd);
			return STATUS_OK;
		}
		else if (fc_ret == STATUS_EOF)
		{
			pfree(fc_passwd);
			return STATUS_ERROR;
		}

		/*
		 * secret, port and identifiers either have length 0 (use default),
		 * length 1 (use the same everywhere) or the same length as servers.
		 * So if the length is >1, we advance one step. In other cases, we
		 * don't and will then reuse the correct value.
		 */
		if (list_length(fc_port->hba->radiussecrets) > 1)
			fc_secrets = lnext(fc_port->hba->radiussecrets, fc_secrets);
		if (list_length(fc_port->hba->radiusports) > 1)
			fc_radiusports = lnext(fc_port->hba->radiusports, fc_radiusports);
		if (list_length(fc_port->hba->radiusidentifiers) > 1)
			fc_identifiers = lnext(fc_port->hba->radiusidentifiers, fc_identifiers);
	}

	/* No servers left to try, so give up */
	pfree(fc_passwd);
	return STATUS_ERROR;
}

static int fc_PerformRadiusTransaction(const char *fc_server, const char *fc_secret, const char *fc_portstr, const char *fc_identifier, const char *fc_user_name, const char *fc_passwd)
{
	radius_packet fc_radius_send_pack;
	radius_packet fc_radius_recv_pack;
	radius_packet *fc_packet = &fc_radius_send_pack;
	radius_packet *fc_receivepacket = &fc_radius_recv_pack;
	char	   *fc_radius_buffer = (char *) &fc_radius_send_pack;
	char	   *fc_receive_buffer = (char *) &fc_radius_recv_pack;
	int32		fc_service = pg_hton32(RADIUS_AUTHENTICATE_ONLY);
	uint8	   *fc_cryptvector;
	int			fc_encryptedpasswordlen;
#ifdef FDD //cppcheck
	uint8		fc_encryptedpassword[RADIUS_MAX_PASSWORD_LENGTH]= {'\0'};
#else
	uint8		fc_encryptedpassword[RADIUS_MAX_PASSWORD_LENGTH];
#endif
	uint8	   *fc_md5trailer;
	int			fc_packetlength;
	pgsocket	fc_sock;

#ifdef HAVE_IPV6
	struct sockaddr_in6 fc_localaddr;
	struct sockaddr_in6 fc_remoteaddr;
#else
	struct sockaddr_in fc_localaddr;
	struct sockaddr_in fc_remoteaddr;
#endif
	struct addrinfo fc_hint;
	struct addrinfo *fc_serveraddrs;
	int			fc_port;
	socklen_t	fc_addrsize;
	fd_set		fc_fdset;
	struct timeval fc_endtime;
	int			fc_i,
				fc_j,
				fc_r;

	/* Assign default values */
	if (fc_portstr == NULL)
		fc_portstr = "1812";
	if (fc_identifier == NULL)
		fc_identifier = "postgresql";

	MemSet(&fc_hint, 0, sizeof(fc_hint));
	fc_hint.ai_socktype = SOCK_DGRAM;
	fc_hint.ai_family = AF_UNSPEC;
	fc_port = atoi(fc_portstr);

	fc_r = pg_getaddrinfo_all(fc_server, fc_portstr, &fc_hint, &fc_serveraddrs);
	if (fc_r || !fc_serveraddrs)
	{
		ereport(LOG,
				(errmsg("could not translate RADIUS server name \"%s\" to address: %s",
						fc_server, gai_strerror(fc_r))));
		if (fc_serveraddrs)
			pg_freeaddrinfo_all(fc_hint.ai_family, fc_serveraddrs);
		return STATUS_ERROR;
	}
	/* XXX: add support for multiple returned addresses? */

	/* Construct RADIUS packet */
	fc_packet->code = RADIUS_ACCESS_REQUEST;
	fc_packet->length = RADIUS_HEADER_LENGTH;
	if (!pg_strong_random(fc_packet->vector, RADIUS_VECTOR_LENGTH))
	{
		ereport(LOG,
				(errmsg("could not generate random encryption vector")));
		pg_freeaddrinfo_all(fc_hint.ai_family, fc_serveraddrs);
		return STATUS_ERROR;
	}
	fc_packet->id = fc_packet->vector[0];
	fc_radius_add_attribute(fc_packet, RADIUS_SERVICE_TYPE, (const unsigned char *) &fc_service, sizeof(fc_service));
	fc_radius_add_attribute(fc_packet, RADIUS_USER_NAME, (const unsigned char *) fc_user_name, strlen(fc_user_name));
	fc_radius_add_attribute(fc_packet, RADIUS_NAS_IDENTIFIER, (const unsigned char *) fc_identifier, strlen(fc_identifier));

	/*
	 * RADIUS password attributes are calculated as: e[0] = p[0] XOR
	 * MD5(secret + Request Authenticator) for the first group of 16 octets,
	 * and then: e[i] = p[i] XOR MD5(secret + e[i-1]) for the following ones
	 * (if necessary)
	 */
	fc_encryptedpasswordlen = ((strlen(fc_passwd) + RADIUS_VECTOR_LENGTH - 1) / RADIUS_VECTOR_LENGTH) * RADIUS_VECTOR_LENGTH;
	fc_cryptvector = palloc(strlen(fc_secret) + RADIUS_VECTOR_LENGTH);
	memcpy(fc_cryptvector, fc_secret, strlen(fc_secret));

	/* for the first iteration, we use the Request Authenticator vector */
	fc_md5trailer = fc_packet->vector;
	for (fc_i = 0; fc_i < fc_encryptedpasswordlen; fc_i += RADIUS_VECTOR_LENGTH)
	{
		const char *fc_errstr = NULL;

		memcpy(fc_cryptvector + strlen(fc_secret), fc_md5trailer, RADIUS_VECTOR_LENGTH);

		/*
		 * .. and for subsequent iterations the result of the previous XOR
		 * (calculated below)
		 */
		fc_md5trailer = fc_encryptedpassword + fc_i;

		if (!pg_md5_binary(fc_cryptvector, strlen(fc_secret) + RADIUS_VECTOR_LENGTH,
						   fc_encryptedpassword + fc_i, &fc_errstr))
		{
			ereport(LOG,
					(errmsg("could not perform MD5 encryption of password: %s",
							fc_errstr)));
			pfree(fc_cryptvector);
			pg_freeaddrinfo_all(fc_hint.ai_family, fc_serveraddrs);
			return STATUS_ERROR;
		}

		for (fc_j = fc_i; fc_j < fc_i + RADIUS_VECTOR_LENGTH; fc_j++)
		{
			if (fc_j < strlen(fc_passwd))
				fc_encryptedpassword[fc_j] = fc_passwd[fc_j] ^ fc_encryptedpassword[fc_j];
			else
				fc_encryptedpassword[fc_j] = '\0' ^ fc_encryptedpassword[fc_j];
		}
	}
	pfree(fc_cryptvector);

	fc_radius_add_attribute(fc_packet, RADIUS_PASSWORD, fc_encryptedpassword, fc_encryptedpasswordlen);

	/* Length needs to be in network order on the wire */
	fc_packetlength = fc_packet->length;
	fc_packet->length = pg_hton16(fc_packet->length);

	fc_sock = socket(fc_serveraddrs[0].ai_family, SOCK_DGRAM, 0);
	if (fc_sock == PGINVALID_SOCKET)
	{
		ereport(LOG,
				(errmsg("could not create RADIUS socket: %m")));
		pg_freeaddrinfo_all(fc_hint.ai_family, fc_serveraddrs);
		return STATUS_ERROR;
	}

	memset(&fc_localaddr, 0, sizeof(fc_localaddr));
#ifdef HAVE_IPV6
	fc_localaddr.sin6_family = fc_serveraddrs[0].ai_family;
	fc_localaddr.sin6_addr = in6addr_any;
	if (fc_localaddr.sin6_family == AF_INET6)
		fc_addrsize = sizeof(struct sockaddr_in6);
	else
		fc_addrsize = sizeof(struct sockaddr_in);
#else
	fc_localaddr.sin_family = fc_serveraddrs[0].ai_family;
	fc_localaddr.sin_addr.s_addr = INADDR_ANY;
	fc_addrsize = sizeof(struct sockaddr_in);
#endif

	if (bind(fc_sock, (struct sockaddr *) &fc_localaddr, fc_addrsize))
	{
		ereport(LOG,
				(errmsg("could not bind local RADIUS socket: %m")));
		closesocket(fc_sock);
		pg_freeaddrinfo_all(fc_hint.ai_family, fc_serveraddrs);
		return STATUS_ERROR;
	}

	if (sendto(fc_sock, fc_radius_buffer, fc_packetlength, 0,
			   fc_serveraddrs[0].ai_addr, fc_serveraddrs[0].ai_addrlen) < 0)
	{
		ereport(LOG,
				(errmsg("could not send RADIUS packet: %m")));
		closesocket(fc_sock);
		pg_freeaddrinfo_all(fc_hint.ai_family, fc_serveraddrs);
		return STATUS_ERROR;
	}

	/* Don't need the server address anymore */
	pg_freeaddrinfo_all(fc_hint.ai_family, fc_serveraddrs);

	/*
	 * Figure out at what time we should time out. We can't just use a single
	 * call to select() with a timeout, since somebody can be sending invalid
	 * packets to our port thus causing us to retry in a loop and never time
	 * out.
	 *
	 * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
	 * the latch was set would improve the responsiveness to
	 * timeouts/cancellations.
	 */
	gettimeofday(&fc_endtime, NULL);
	fc_endtime.tv_sec += RADIUS_TIMEOUT;

	while (true)
	{
		struct timeval fc_timeout;
		struct timeval fc_now;
		int64		fc_timeoutval;
		const char *fc_errstr = NULL;

		gettimeofday(&fc_now, NULL);
		fc_timeoutval = (fc_endtime.tv_sec * 1000000 + fc_endtime.tv_usec) - (fc_now.tv_sec * 1000000 + fc_now.tv_usec);
		if (fc_timeoutval <= 0)
		{
			ereport(LOG,
					(errmsg("timeout waiting for RADIUS response from %s",
							fc_server)));
			closesocket(fc_sock);
			return STATUS_ERROR;
		}
		fc_timeout.tv_sec = fc_timeoutval / 1000000;
		fc_timeout.tv_usec = fc_timeoutval % 1000000;

		FD_ZERO(&fc_fdset);
		FD_SET(fc_sock, &fc_fdset);

		fc_r = select(fc_sock + 1, &fc_fdset, NULL, NULL, &fc_timeout);
		if (fc_r < 0)
		{
			if (errno == EINTR)
				continue;

			/* Anything else is an actual error */
			ereport(LOG,
					(errmsg("could not check status on RADIUS socket: %m")));
			closesocket(fc_sock);
			return STATUS_ERROR;
		}
		if (fc_r == 0)
		{
			ereport(LOG,
					(errmsg("timeout waiting for RADIUS response from %s",
							fc_server)));
			closesocket(fc_sock);
			return STATUS_ERROR;
		}

		/*
		 * Attempt to read the response packet, and verify the contents.
		 *
		 * Any packet that's not actually a RADIUS packet, or otherwise does
		 * not validate as an explicit reject, is just ignored and we retry
		 * for another packet (until we reach the timeout). This is to avoid
		 * the possibility to denial-of-service the login by flooding the
		 * server with invalid packets on the port that we're expecting the
		 * RADIUS response on.
		 */

		fc_addrsize = sizeof(fc_remoteaddr);
		fc_packetlength = recvfrom(fc_sock, fc_receive_buffer, RADIUS_BUFFER_SIZE, 0,
								(struct sockaddr *) &fc_remoteaddr, &fc_addrsize);
		if (fc_packetlength < 0)
		{
			ereport(LOG,
					(errmsg("could not read RADIUS response: %m")));
			closesocket(fc_sock);
			return STATUS_ERROR;
		}

#ifdef HAVE_IPV6
		if (fc_remoteaddr.sin6_port != pg_hton16(fc_port))
#else
		if (fc_remoteaddr.sin_port != pg_hton16(fc_port))
#endif
		{
#ifdef HAVE_IPV6
			ereport(LOG,
					(errmsg("RADIUS response from %s was sent from incorrect port: %d",
							fc_server, pg_ntoh16(fc_remoteaddr.sin6_port))));
#else
			ereport(LOG,
					(errmsg("RADIUS response from %s was sent from incorrect port: %d",
							fc_server, pg_ntoh16(fc_remoteaddr.sin_port))));
#endif
			continue;
		}

		if (fc_packetlength < RADIUS_HEADER_LENGTH)
		{
			ereport(LOG,
					(errmsg("RADIUS response from %s too short: %d", fc_server, fc_packetlength)));
			continue;
		}

		if (fc_packetlength != pg_ntoh16(fc_receivepacket->length))
		{
			ereport(LOG,
					(errmsg("RADIUS response from %s has corrupt length: %d (actual length %d)",
							fc_server, pg_ntoh16(fc_receivepacket->length), fc_packetlength)));
			continue;
		}

		if (fc_packet->id != fc_receivepacket->id)
		{
			ereport(LOG,
					(errmsg("RADIUS response from %s is to a different request: %d (should be %d)",
							fc_server, fc_receivepacket->id, fc_packet->id)));
			continue;
		}

		/*
		 * Verify the response authenticator, which is calculated as
		 * MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
		 */
		fc_cryptvector = palloc(fc_packetlength + strlen(fc_secret));

		memcpy(fc_cryptvector, fc_receivepacket, 4);	/* code+id+length */
		memcpy(fc_cryptvector + 4, fc_packet->vector, RADIUS_VECTOR_LENGTH);	/* request
																		 * authenticator, from
																		 * original packet */
		if (fc_packetlength > RADIUS_HEADER_LENGTH)	/* there may be no
													 * attributes at all */
			memcpy(fc_cryptvector + RADIUS_HEADER_LENGTH, fc_receive_buffer + RADIUS_HEADER_LENGTH, fc_packetlength - RADIUS_HEADER_LENGTH);
		memcpy(fc_cryptvector + fc_packetlength, fc_secret, strlen(fc_secret));

		if (!pg_md5_binary(fc_cryptvector,
						   fc_packetlength + strlen(fc_secret),
						   fc_encryptedpassword, &fc_errstr))
		{
			ereport(LOG,
					(errmsg("could not perform MD5 encryption of received packet: %s",
							fc_errstr)));
			pfree(fc_cryptvector);
			continue;
		}
		pfree(fc_cryptvector);

		if (memcmp(fc_receivepacket->vector, fc_encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
		{
			ereport(LOG,
					(errmsg("RADIUS response from %s has incorrect MD5 signature",
							fc_server)));
			continue;
		}

		if (fc_receivepacket->code == RADIUS_ACCESS_ACCEPT)
		{
			closesocket(fc_sock);
			return STATUS_OK;
		}
		else if (fc_receivepacket->code == RADIUS_ACCESS_REJECT)
		{
			closesocket(fc_sock);
			return STATUS_EOF;
		}
		else
		{
			ereport(LOG,
					(errmsg("RADIUS response from %s has invalid code (%d) for user \"%s\"",
							fc_server, fc_receivepacket->code, fc_user_name)));
			continue;
		}
	}							/* while (true) */
}
